Improve multiselect

This commit is contained in:
Jackson Harper
2023-08-03 13:21:46 +08:00
parent ebbc14f841
commit 97f422cf3f
15 changed files with 421 additions and 251 deletions

View File

@ -11,9 +11,8 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
return (
<Box
css={{
height: '4px',
height: '5px',
width: '100%',
borderRadius: '$1',
overflow: 'hidden',
backgroundColor: props.backgroundColor,
}}
@ -23,7 +22,6 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
height: '100%',
width: `${props.fillPercentage}%`,
backgroundColor: props.fillColor,
borderRadius: props.borderRadius,
}}
/>
</Box>

View File

@ -1,6 +1,7 @@
import { LayoutType } from '../../templates/homeFeed/HomeFeedContainer'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import type { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { MultiSelectMode } from '../../templates/homeFeed/LibraryHeader'
export type LinkedItemCardAction =
| 'showDetail'
@ -23,9 +24,10 @@ export type LinkedItemCardProps = {
handleAction: (action: LinkedItemCardAction) => void
inMultiSelect: boolean
isChecked: boolean
setIsChecked: (itemId: string, set: boolean) => void
multiSelectMode: MultiSelectMode
isHovered?: boolean
}

View File

@ -1,8 +1,8 @@
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useMemo } from 'react'
import { ChangeEvent, useMemo } from 'react'
import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
import { Box, SpanBox, VStack } from '../../elements/LayoutPrimitives'
dayjs.extend(relativeTime)
@ -25,7 +25,7 @@ export const MenuStyle = {
export const MetaStyle = {
width: '100%',
color: '$thTextSubtle3',
fontSize: '13px',
fontSize: '12px',
fontWeight: '400',
fontFamily: '$display',
}
@ -34,7 +34,7 @@ export const TitleStyle = {
color: '$thTextContrast2',
fontSize: '16px',
fontWeight: '700',
lineHeight: '1.25',
lineHeight: '1',
fontFamily: '$display',
overflow: 'hidden',
textOverflow: 'ellipsis',
@ -67,11 +67,11 @@ export const AuthorInfoStyle = {
whiteSpace: 'nowrap',
maxWidth: '240px',
overflow: 'hidden',
height: '21px',
color: '$thTextSubtle3',
fontSize: '12px',
fontWeight: '400',
fontFamily: '$display',
lineHeight: '1',
}
export const timeAgo = (date: string | undefined): string => {
@ -137,14 +137,6 @@ export function LibraryItemMetadata(
{highlightCount > 0
? `${highlightCount} highlight${highlightCount > 1 ? 's' : ''}`
: null}
{(props.showProgress && props.item.readingProgressPercent) ?? 0 > 0 ? (
<>
{` | `}
<SpanBox css={{ color: '#55B938' }}>
{`${Math.round(props.item.readingProgressPercent)}%`}
</SpanBox>
</>
) : null}
</Box>
)
}
@ -156,10 +148,19 @@ type CardCheckBoxProps = {
export function CardCheckbox(props: CardCheckBoxProps): JSX.Element {
return (
<input
type="checkbox"
checked={props.isChecked}
onChange={props.handleChanged}
></input>
<form
// This prevents us from propogating up the the <a element on cards
onClick={(event) => {
event.stopPropagation()
}}
>
<input
type="checkbox"
checked={props.isChecked}
onChange={(event) => {
props.handleChanged()
}}
></input>
</form>
)
}

View File

@ -4,7 +4,7 @@ import type { LinkedItemCardProps } from './CardTypes'
import { CoverImage } from '../../elements/CoverImage'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useCallback, useState } from 'react'
import { ChangeEvent, useCallback, useState } from 'react'
import Link from 'next/link'
import {
AuthorInfoStyle,
@ -29,6 +29,8 @@ import {
import { CardMenu } from '../CardMenu'
import { DotsThree } from 'phosphor-react'
import { isTouchScreenDevice } from '../../../lib/deviceType'
import { ProgressBarOverlay } from './LibraryListCard'
import { FallbackImage } from './FallbackImage'
dayjs.extend(relativeTime)
@ -115,38 +117,32 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
setIsHovered(false)
}}
>
{props.inMultiSelect ? (
<LibraryGridCardContent {...props} isHovered={isHovered} />
) : (
<>
{!isTouchScreenDevice() && (
<Box
ref={refs.setFloating}
style={{ ...floatingStyles, zIndex: 10 }}
{...getFloatingProps()}
>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
</Box>
)}
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
>
<a
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
>
<LibraryGridCardContent {...props} isHovered={isHovered} />
</a>
</Link>
</>
{!isTouchScreenDevice() && (
<Box
ref={refs.setFloating}
style={{ ...floatingStyles, zIndex: 10 }}
{...getFloatingProps()}
>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
</Box>
)}
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
>
<a
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
>
<LibraryGridCardContent {...props} isHovered={isHovered} />
</a>
</Link>
</VStack>
)
}
@ -189,25 +185,43 @@ const Fallback = (props: FallbackProps): JSX.Element => {
type GridImageProps = {
src?: string
title?: string
readingProgress?: number
}
const GridImage = (props: GridImageProps): JSX.Element => {
const [displayFallback, setDisplayFallback] = useState(props.src == undefined)
return displayFallback ? (
<Fallback title={props.title ?? 'Omnivore Fallback'} />
) : (
<CoverImage
src={props.src}
width="100%"
height={100}
css={{
bg: '$thBackground',
}}
onError={(e) => {
setDisplayFallback(true)
}}
/>
return (
<>
{(props.readingProgress ?? 0) > 0 && (
<ProgressBarOverlay
width="100%"
top={95}
value={props.readingProgress ?? 0}
bottomRadius={'0px'}
/>
)}
{displayFallback ? (
<FallbackImage
title={props.title ?? 'Omnivore Fallback'}
width="100%"
height="100px"
fontSize="128px"
/>
) : (
<CoverImage
src={props.src}
width="100%"
height={100}
css={{
bg: '$thBackground',
}}
onError={(e) => {
setDisplayFallback(true)
}}
/>
)}
</>
)
}
@ -217,45 +231,55 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
const originText = siteName(props.item.originalArticleUrl, props.item.url)
const handleCheckChanged = useCallback(() => {
setIsChecked(item.id, !isChecked)
}, [setIsChecked, isChecked])
const newValue = !isChecked
setIsChecked(item.id, newValue)
}, [setIsChecked, isChecked, props])
return (
<VStack css={{ p: '0px', m: '0px', width: '100%' }}>
<Box css={{ position: 'relative', width: '100%' }}>
<GridImage src={props.item.image} title={props.item.title} />
{props.inMultiSelect ? (
<SpanBox css={{ position: 'absolute', top: 0, left: 0, m: '5px' }}>
<CardCheckbox
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>
) : (
<Box
css={{
...MenuStyle,
position: 'absolute',
top: 0,
right: 0,
m: '5px',
visibility: menuOpen ? 'visible' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<DotsThree size={25} weight="bold" color="#ADADAD" />
}
/>
</Box>
)}
<GridImage
src={props.item.image}
title={props.item.title}
readingProgress={item.readingProgressPercent}
/>
<SpanBox
css={{
position: 'absolute',
top: 0,
left: 0,
m: '10px',
lineHeight: '1',
}}
>
<CardCheckbox
isChecked={isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>
<Box
css={{
...MenuStyle,
position: 'absolute',
top: 0,
right: 0,
m: '5px',
visibility: menuOpen ? 'visible' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<DotsThree size={25} weight="bold" color="#ADADAD" />
}
/>
</Box>
</Box>
<HStack
@ -299,7 +323,7 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
<HStack
distribution="start"
alignment="start"
css={{ width: '100%', minHeight: '50px' }}
css={{ width: '100%', minHeight: '50px', pb: '10px' }}
>
<HStack
css={{

View File

@ -38,7 +38,6 @@ export function LibraryHighlightGridCard(
showErrorToast('Error navigating to highlight')
return
}
console.log('pushing user: ', props.viewer, 'slug: ', props.item.slug)
router.push(
{
pathname: '/[username]/[slug]',

View File

@ -32,8 +32,6 @@ type LibraryHoverActionsProps = {
export const LibraryHoverActions = (props: LibraryHoverActionsProps) => {
const [menuOpen, setMenuOpen] = useState(false)
console.log(' props.isHovered || menuOpen', props.isHovered, menuOpen)
return (
<Box
css={{

View File

@ -11,6 +11,7 @@ import {
siteName,
TitleStyle,
MenuStyle,
Fallback,
} from './LibraryCardStyles'
import { sortedLabels } from '../../../lib/labelsSort'
import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/homeFeed/LibraryFilterMenu'
@ -26,6 +27,10 @@ import {
import { CardMenu } from '../CardMenu'
import { DotsThree } from 'phosphor-react'
import { isTouchScreenDevice } from '../../../lib/deviceType'
import { CoverImage } from '../../elements/CoverImage'
import { ProgressBar } from '../../elements/ProgressBar'
import { theme } from '../../tokens/stitches.config'
import { FallbackImage } from './FallbackImage'
export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
@ -55,6 +60,7 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
{...getReferenceProps()}
css={{
px: '20px',
pl: '10px',
pt: '20px',
pb: '20px',
height: '100%',
@ -92,42 +98,112 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
setIsHovered(false)
}}
>
{props.inMultiSelect ? (
<LibraryListCardContent {...props} isHovered={isHovered} />
) : (
<>
{!isTouchScreenDevice() && (
<Box
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
</Box>
)}
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
>
<a
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
>
<LibraryListCardContent {...props} isHovered={isHovered} />
</a>
</Link>
</>
{!isTouchScreenDevice() && (
<Box
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
<LibraryHoverActions
item={props.item}
viewer={props.viewer}
handleAction={props.handleAction}
isHovered={isHovered ?? false}
/>
</Box>
)}
<Link
href={`${props.viewer.profile.username}/${props.item.slug}`}
passHref
>
<a
href={`${props.viewer.profile.username}/${props.item.slug}`}
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
tabIndex={-1}
>
<LibraryListCardContent {...props} isHovered={isHovered} />
</a>
</Link>
</VStack>
)
}
type ProgressBarOverlayProps = {
top: number
width: string
value: number
bottomRadius: string
}
export const ProgressBarOverlay = (
props: ProgressBarOverlayProps
): JSX.Element => {
return (
<Box
css={{
position: 'absolute',
width: props.width,
top: props.top,
borderBottomLeftRadius: props.bottomRadius,
borderBottomRightRadius: props.bottomRadius,
overflow: 'clip',
zIndex: 20,
}}
>
<ProgressBar
fillPercentage={props.value}
fillColor={theme.colors.thProgressFg.toString()}
backgroundColor="rgba(217, 217, 217, 0.65)"
borderRadius={'2px'}
/>
</Box>
)
}
type ListImageProps = {
src?: string
title?: string
readingProgress?: number
}
const ListImage = (props: ListImageProps): JSX.Element => {
const [displayFallback, setDisplayFallback] = useState(props.src == undefined)
return (
<>
{(props.readingProgress ?? 0) > 0 && (
<ProgressBarOverlay
width="55px"
top={50}
bottomRadius="4px"
value={props.readingProgress ?? 0}
/>
)}
{displayFallback ? (
<FallbackImage
title={props.title ?? 'Omnivore Fallback'}
width="55px"
height="55px"
fontSize="36pt"
/>
) : (
<CoverImage
src={props.src}
width={55}
height={55}
css={{
bg: '$thBackground',
borderRadius: '4px',
}}
onError={() => {
setDisplayFallback(true)
}}
/>
)}
</>
)
}
export function LibraryListCardContent(
props: LinkedItemCardProps
): JSX.Element {
@ -140,57 +216,96 @@ export function LibraryListCardContent(
}, [isChecked, setIsChecked, item])
return (
<>
<HStack css={MetaStyle} distribution="start">
<LibraryItemMetadata item={props.item} showProgress={true} />
{props.inMultiSelect ? (
<SpanBox css={{ marginLeft: 'auto' }}>
<CardCheckbox
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>
) : (
<Box
css={{
...MenuStyle,
visibility: menuOpen ? 'visible' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<DotsThree size={25} weight="bold" color="#ADADAD" />
}
/>
</Box>
)}
</HStack>
<HStack css={{ gap: '15px', width: '100%' }}>
<SpanBox
css={{
display: 'flex',
m: '0px',
mt: '0px',
p: '0px',
ml: '4px',
lineHeight: '1',
'> input': {
p: '0px',
m: '0px',
},
}}
>
<CardCheckbox
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>
<Box css={{ position: 'relative', width: '55px' }}>
<ListImage
src={props.item.image}
title={props.item.title}
readingProgress={item.readingProgressPercent}
/>
<Box
css={{
...MenuStyle,
position: 'absolute',
top: 0,
right: 0,
m: '5px',
visibility: menuOpen ? 'visible' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
onOpenChange={(open) => setMenuOpen(open)}
actionHandler={props.handleAction}
triggerElement={
<DotsThree size={25} weight="bold" color="#ADADAD" />
}
/>
</Box>
</Box>
<VStack
alignment="start"
distribution="start"
css={{ height: '100%', width: '100%' }}
css={{ height: '100%', width: '100%', lineHeight: 1, gap: '5px' }}
>
<Box css={{ ...TitleStyle, fontSize: '18px', width: '80%' }}>
{props.item.title}
</Box>
<SpanBox
<VStack
alignment="start"
distribution="center"
css={{
mt: '5px',
...AuthorInfoStyle,
maxWidth: '90%',
height: '55px',
width: '100%',
justifyContent: 'space-between',
}}
>
{props.item.author}
{props.item.author && originText && ' | '}
<SpanBox css={{ textDecoration: 'underline' }}>{originText}</SpanBox>
</SpanBox>
<HStack
css={{
...MetaStyle,
color: '$grayText',
}}
distribution="start"
>
<LibraryItemMetadata item={props.item} showProgress={true} />
</HStack>
<Box css={{ ...TitleStyle, width: '80%' }}>{props.item.title}</Box>
<SpanBox
css={{
...AuthorInfoStyle,
maxWidth: '90%',
minHeight: '12px',
}}
>
{props.item.author}
{props.item.author && originText && ' | '}
<SpanBox css={{ textDecoration: 'underline' }}>
{originText}
</SpanBox>
</SpanBox>
</VStack>
<HStack
distribution="start"
@ -209,6 +324,6 @@ export function LibraryListCardContent(
</HStack>
</HStack>
</VStack>
</>
</HStack>
)
}

View File

@ -11,6 +11,7 @@ import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
import { setupAnalytics } from '../../lib/analytics'
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { theme } from '../tokens/stitches.config'
type PrimaryLayoutProps = {
children: ReactNode

View File

@ -130,7 +130,6 @@ export function UploadModal(props: UploadModalProps): JSX.Element {
Papa.parse(file.file, {
step: function (row, parser) {
console.log('row: ', row)
if (Array.isArray(row.data)) {
try {
if (row.data[0].trim().length < 1) {
@ -211,7 +210,6 @@ export function UploadModal(props: UploadModalProps): JSX.Element {
;(async () => {
for (const file of addedFiles) {
try {
console.log('using content type: ', file.file.type)
const uploadInfo = await uploadSignedUrlForFile(file)
if (!uploadInfo.uploadSignedUrl) {
showErrorToast('No upload URL available')

View File

@ -192,7 +192,6 @@ export function Article(props: ArticleProps): JSX.Element {
const img = element as HTMLImageElement
const width = Number(img.getAttribute('data-omnivore-width'))
const height = Number(img.getAttribute('data-omnivore-height'))
console.log('width and height: ', width, height)
if (!isNaN(width) && !isNaN(height) && width < 100 && height < 100) {
img.style.setProperty('width', `${width}px`)

View File

@ -297,7 +297,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const { target, pageX, pageY } = event
if (!target || (target as Node)?.nodeType !== Node.ELEMENT_NODE) {
console.log(' -- returning early from page tap')
return
}
@ -375,7 +374,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
highlightIdAttribute
)
const highlight = highlights.find(($0) => $0.id === id)
console.log('double tapped highlight: ', highlight)
setFocusedHighlight(highlight)
openNoteModal({
@ -387,8 +385,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
highlightNoteIdAttribute
)
const highlight = highlights.find(($0) => $0.id === id)
console.log('double tapped highlight with note: ', highlight)
setFocusedHighlight(highlight)
openNoteModal({

View File

@ -91,7 +91,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
)
const createNote = useCallback((text: string) => {
console.log('creating note: ', newNoteId, noteState.current.isCreating)
noteState.current.isCreating = true
noteState.current.createStarted = new Date()
;(async () => {

View File

@ -84,11 +84,13 @@ export function HomeFeedContainer(): JSX.Element {
const gridContainerRef = useRef<HTMLDivElement>(null)
const [labelsTarget, setLabelsTarget] =
useState<LibraryItem | undefined>(undefined)
const [labelsTarget, setLabelsTarget] = useState<LibraryItem | undefined>(
undefined
)
const [notebookTarget, setNotebookTarget] =
useState<LibraryItem | undefined>(undefined)
const [notebookTarget, setNotebookTarget] = useState<LibraryItem | undefined>(
undefined
)
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
@ -372,15 +374,12 @@ export function HomeFeedContainer(): JSX.Element {
const [multiSelectMode, setMultiSelectMode] = useState<MultiSelectMode>('off')
const selectActiveArticle = useCallback(() => {
console.log('selecting article: ', activeItem)
if (activeItem) {
if (multiSelectMode === 'off') {
console.log('setting ')
setMultiSelectMode('some')
}
const itemId = activeItem.node.id
const isChecked = itemIsChecked(itemId)
console.log('setting is checked: ', isChecked, itemId)
setIsChecked(itemId, !isChecked)
}
}, [activeItem, multiSelectMode, checkedItems])
@ -493,7 +492,6 @@ export function HomeFeedContainer(): JSX.Element {
handleCardAction('set-labels', activeItem)
break
case 'openNotebook':
console.log('openNotebook: ', notebookTarget)
handleCardAction('open-notebook', activeItem)
break
case 'sortDescending':
@ -553,7 +551,6 @@ export function HomeFeedContainer(): JSX.Element {
name: 'Mark item as read',
shortcut: ['m', 'r'],
perform: () => {
console.log('mark read action')
handleCardAction('mark-read', activeItem)
},
}),
@ -612,8 +609,16 @@ export function HomeFeedContainer(): JSX.Element {
checkedItems.splice(checkedItems.indexOf(itemId), 1)
setCheckedItems([...checkedItems])
}
if (set && multiSelectMode == 'off') {
setMultiSelectMode('some')
}
if (checkedItems.length < 1) {
setMultiSelectMode('off')
}
},
[checkedItems]
[checkedItems, multiSelectMode, setMultiSelectMode]
)
useEffect(() => {
@ -806,10 +811,12 @@ type HomeFeedContentProps = {
item: LibraryItem | undefined
) => Promise<void>
multiSelectMode: MultiSelectMode
setIsChecked: (itemId: string, set: boolean) => void
itemIsChecked: (itemId: string) => boolean
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
numItemsSelected: number
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
@ -861,7 +868,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
setShowAddLinkModal={props.setShowAddLinkModal}
searchTerm={props.searchTerm}
applySearchQuery={(searchQuery: string) => {
console.log('searching with searchQuery: ', searchQuery)
props.applySearchQuery(searchQuery)
}}
showFilterMenu={showFilterMenu}
@ -880,7 +886,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
<LibraryItemsLayout
viewer={viewerData?.me}
layout={layout}
inMultiSelect={props.multiSelectMode !== 'off'}
isChecked={props.itemIsChecked}
{...props}
/>
@ -897,7 +902,9 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
type LibraryItemsLayoutProps = {
layout: LayoutType
viewer?: UserBasicData
inMultiSelect: boolean
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
isChecked: (itemId: string) => boolean
setIsChecked: (itemId: string, set: boolean) => void
@ -970,7 +977,7 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
setLinkToUnsubscribe={props.setLinkToUnsubscribe}
setShowRemoveLinkConfirmation={setShowRemoveLinkConfirmation}
actionHandler={props.actionHandler}
inMultiSelect={props.inMultiSelect}
multiSelectMode={props.multiSelectMode}
/>
)}
<HStack
@ -1024,7 +1031,8 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
item={props.linkToRemove?.node}
viewer={props.viewer}
layout="GRID_LAYOUT"
inMultiSelect={false}
multiSelectMode={props.multiSelectMode}
setMultiSelectMode={props.setMultiSelectMode}
isChecked={false}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setIsChecked={() => {}}
@ -1095,9 +1103,9 @@ type LibraryItemsProps = {
setLinkToUnsubscribe: (set: LibraryItem | undefined) => void
setShowRemoveLinkConfirmation: (show: true) => void
inMultiSelect: boolean
isChecked: (itemId: string) => boolean
setIsChecked: (itemId: string, set: boolean) => void
multiSelectMode: MultiSelectMode
actionHandler: (
action: LinkedItemCardAction,
@ -1186,7 +1194,7 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
viewer={props.viewer}
isChecked={props.isChecked(linkedItem.node.id)}
setIsChecked={props.setIsChecked}
inMultiSelect={props.inMultiSelect}
multiSelectMode={props.multiSelectMode}
handleAction={(action: LinkedItemCardAction) => {
if (action === 'delete') {
props.setShowRemoveLinkConfirmation(true)

View File

@ -28,6 +28,7 @@ import { TrashIcon } from '../../elements/icons/TrashIcon'
import { LabelIcon } from '../../elements/icons/LabelIcon'
import { ListViewIcon } from '../../elements/icons/ListViewIcon'
import { GridViewIcon } from '../../elements/icons/GridViewIcon'
import { CaretDownIcon } from '../../elements/icons/CaretDownIcon'
export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search'
@ -377,9 +378,7 @@ type ControlButtonBoxProps = {
applySearchQuery: (searchQuery: string) => void
}
function MultiSelectControlButtonBox(
props: ControlButtonBoxProps
): JSX.Element {
function MultiSelectControls(props: ControlButtonBoxProps): JSX.Element {
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showLabelsModal, setShowLabelsModal] = useState(false)
@ -498,7 +497,7 @@ function SearchControlButtonBox(
)
}
function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
const MuliSelectControl = (props: ControlButtonBoxProps): JSX.Element => {
const [isChecked, setIsChecked] = useState(false)
useEffect(() => {
@ -507,6 +506,69 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
}
}, [props.multiSelectMode])
return (
<Box
css={{
display: 'flex',
padding: '10px',
height: '38px',
maxWidth: '521px',
bg: '$thLibrarySearchbox',
borderRadius: '6px',
boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.05);',
'@mdDown': {
mx: '5px',
},
}}
>
<SpanBox
css={{
flex: 1,
display: 'flex',
gap: '5px',
alignItems: 'center',
}}
>
<CardCheckbox
isChecked={isChecked}
handleChanged={() => {
const newValue = !isChecked
props.setMultiSelectMode(newValue ? 'visible' : 'off')
setIsChecked(newValue)
}}
/>
<SpanBox css={{ display: 'flex', pb: '2px' }}>
<Dropdown
triggerElement={
<CaretDownIcon
size={9}
color={theme.colors.graySolid.toString()}
/>
}
>
<DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('visible')
}}
title="All"
/>
{/* <DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('search')
}}
title="All matching search"
/> */}
</Dropdown>
</SpanBox>
</SpanBox>
</Box>
)
}
function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
return (
<>
<HStack
@ -519,7 +581,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
},
width: '95%',
'@media (min-width: 930px)': {
width: '640px',
width: '660px',
},
'@media (min-width: 1280px)': {
width: '1000px',
@ -529,6 +591,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
},
}}
>
<MuliSelectControl {...props} />
{props.multiSelectMode !== 'off' && (
<SpanBox
css={{
@ -541,42 +604,9 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
},
}}
>
<CardCheckbox
isChecked={isChecked}
handleChanged={() => {
const newValue = !isChecked
props.setMultiSelectMode(newValue ? 'visible' : 'none')
setIsChecked(newValue)
}}
/>
<SpanBox css={{ pt: '2px' }}>
<Dropdown
triggerElement={
<CaretDown
size={15}
color={theme.colors.graySolid.toString()}
weight="fill"
/>
}
>
<DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('visible')
}}
title="All"
/>
<DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('search')
}}
title="All matching search"
/>
</Dropdown>
</SpanBox>
<SpanBox
css={{
color: '#55B938',
paddingLeft: '5px',
fontSize: '12px',
fontWeight: '600',
@ -588,7 +618,9 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
>
{props.numItemsSelected}{' '}
<SpanBox
css={{ '@media (max-width: 1280px)': { display: 'none' } }}
css={{
'@media (max-width: 1280px)': { display: 'none' },
}}
>
selected
</SpanBox>
@ -597,7 +629,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
)}
{props.multiSelectMode !== 'off' ? (
<>
<MultiSelectControlButtonBox {...props} />
<MultiSelectControls {...props} />
<SpanBox css={{ flex: 1 }}></SpanBox>
</>
) : (

View File

@ -6,7 +6,7 @@ const ContentSecurityPolicy = `
font-src 'self' data: cdn.jsdelivr.net https://js.intercomcdn.com https://fonts.intercomcdn.com;
form-action 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://getpocket.com/auth/authorize https://intercom.help https://api-iam.intercom.io https://api-iam.eu.intercom.io https://api-iam.au.intercom.io;
frame-ancestors 'none';
frame-src self accounts.google.com platform.twitter.com www.youtube.com www.youtube-nocookie.com;
frame-src 'self' accounts.google.com platform.twitter.com www.youtube.com www.youtube-nocookie.com;
manifest-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' accounts.google.com widget.intercom.io js.intercomcdn.com platform.twitter.com cdnjs.cloudflare.com cdn.jsdelivr.net cdn.segment.com;
style-src 'self' 'unsafe-inline' accounts.google.com cdnjs.cloudflare.com;