More work on multiple selection mode

This commit is contained in:
Jackson Harper
2023-06-08 12:47:04 +08:00
parent 83c8aff44d
commit e0cdbdb0e7
7 changed files with 194 additions and 56 deletions

View File

@ -19,8 +19,12 @@ export type LinkedItemCardProps = {
item: LibraryItemNode
layout: LayoutType
viewer: UserBasicData
inMultiSelect: boolean
handleAction: (action: LinkedItemCardAction) => void
inMultiSelect: boolean
isChecked: boolean
setIsChecked: (itemId: string, set: boolean) => void
isHovered?: boolean
}

View File

@ -105,13 +105,11 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
const [menuOpen, setMenuOpen] = useState(false)
const [isChecked, setIsChecked] = useState(false)
const originText = siteName(props.item.originalArticleUrl, props.item.url)
const handleCheckChanged = useCallback(() => {
setIsChecked(!isChecked)
}, [isChecked])
props.setIsChecked(props.item.id, !props.isChecked)
}, [props.isChecked])
return (
<>
@ -126,7 +124,7 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
{props.inMultiSelect ? (
<SpanBox css={{ marginLeft: 'auto' }}>
<CardCheckbox
isChecked={isChecked}
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>

View File

@ -74,13 +74,11 @@ export function LibraryListCardContent(
props: LinkedItemCardProps
): JSX.Element {
const [menuOpen, setMenuOpen] = useState(false)
const [isChecked, setIsChecked] = useState(false)
const originText = siteName(props.item.originalArticleUrl, props.item.url)
const handleCheckChanged = useCallback(() => {
setIsChecked(!isChecked)
}, [isChecked])
props.setIsChecked(props.item.id, !props.isChecked)
}, [props.isChecked])
return (
<>
@ -89,7 +87,7 @@ export function LibraryListCardContent(
{props.inMultiSelect ? (
<SpanBox css={{ marginLeft: 'auto' }}>
<CardCheckbox
isChecked={isChecked}
isChecked={props.isChecked}
handleChanged={handleCheckChanged}
/>
</SpanBox>

View File

@ -24,6 +24,7 @@ type PrimaryDropdownProps = {
layout?: LayoutType
updateLayout?: (layout: LayoutType) => void
showAddLinkModal?: () => void
startSelectMultiple?: () => void
}
@ -37,7 +38,6 @@ export type HeaderDropdownAction =
| 'navigate-to-integrations'
| 'increaseFontSize'
| 'decreaseFontSize'
| 'begin-select-multiple'
| 'logout'
export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element {
@ -65,11 +65,6 @@ export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element {
case 'navigate-to-integrations':
router.push('/settings/integrations')
break
case 'begin-select-multiple':
if (props.startSelectMultiple) {
props.startSelectMultiple()
}
break
case 'logout':
document.dispatchEvent(new Event('logout'))
break
@ -161,12 +156,21 @@ export function PrimaryDropdown(props: PrimaryDropdownProps): JSX.Element {
onSelect={() => headerDropdownActionHandler('navigate-to-labels')}
title="Labels"
/>
<DropdownSeparator />
{props.startSelectMultiple && (
<DropdownOption
onSelect={() => headerDropdownActionHandler('begin-select-multiple')}
onSelect={() =>
props.startSelectMultiple && props.startSelectMultiple()
}
title="Select Multiple"
/>
)}
{props.showAddLinkModal && (
<DropdownOption
onSelect={() => props.showAddLinkModal && props.showAddLinkModal()}
title="Add Link"
/>
)}
<DropdownSeparator />
<DropdownOption

View File

@ -40,7 +40,7 @@ import { EditLibraryItemModal } from './EditItemModals'
import { EmptyLibrary } from './EmptyLibrary'
import { HighlightItemsLayout } from './HighlightsLayout'
import { LibraryFilterMenu } from './LibraryFilterMenu'
import { LibraryHeader } from './LibraryHeader'
import { LibraryHeader, MultiSelectMode } from './LibraryHeader'
import { UploadModal } from '../UploadModal'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
@ -116,6 +116,7 @@ export function HomeFeedContainer(): JSX.Element {
} else {
setMode('reads')
}
setMultiSelectMode('off')
}, [queryInputs])
useEffect(() => {
@ -521,11 +522,58 @@ export function HomeFeedContainer(): JSX.Element {
)
useFetchMore(handleFetchMore)
const [checkedItems, setCheckedItems] = useState<string[]>([])
const [multiSelectMode, setMultiSelectMode] = useState<MultiSelectMode>('off')
const setIsChecked = useCallback(
(itemId: string, set: boolean) => {
if (set && checkedItems.indexOf(itemId) === -1) {
checkedItems.push(itemId)
setCheckedItems([...checkedItems])
} else if (!set && checkedItems.indexOf(itemId) !== -1) {
setCheckedItems(checkedItems.splice(checkedItems.indexOf(itemId), 1))
}
},
[checkedItems]
)
useEffect(() => {
console.log('switching on multiselect mode: ', multiSelectMode)
switch (multiSelectMode) {
case 'off':
case 'none':
setCheckedItems([])
break
case 'some':
break
case 'search':
case 'visible':
const allIds = (
itemsPages?.flatMap((ad) => {
return ad.search.edges
}) || []
).map((item) => item.node.id)
setCheckedItems(allIds)
break
}
}, [multiSelectMode])
const itemIsChecked = useCallback(
(itemId: string) => {
return checkedItems.indexOf(itemId) !== -1
},
[checkedItems]
)
return (
<HomeFeedGrid
items={libraryItems}
actionHandler={handleCardAction}
reloadItems={mutate}
setIsChecked={setIsChecked}
itemIsChecked={itemIsChecked}
multiSelectMode={multiSelectMode}
setMultiSelectMode={setMultiSelectMode}
searchTerm={queryInputs.searchQuery}
gridContainerRef={gridContainerRef}
mode={mode}
@ -572,6 +620,11 @@ export function HomeFeedContainer(): JSX.Element {
setLinkToEdit={setLinkToEdit}
linkToUnsubscribe={linkToUnsubscribe}
setLinkToUnsubscribe={setLinkToUnsubscribe}
numItemsSelected={
multiSelectMode == 'search'
? itemsPages?.[0].search.pageInfo.totalCount || 0
: checkedItems.length
}
/>
)
}
@ -609,6 +662,12 @@ type HomeFeedContentProps = {
action: LinkedItemCardAction,
item: LibraryItem | undefined
) => Promise<void>
multiSelectMode: MultiSelectMode
setIsChecked: (itemId: string, set: boolean) => void
itemIsChecked: (itemId: string) => boolean
setMultiSelectMode: (mode: MultiSelectMode) => void
numItemsSelected: number
}
function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
@ -627,7 +686,8 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
)
const [showFilterMenu, setShowFilterMenu] = useState(false)
const [inMultiSelect, setInMultiSelect] = useState(false)
console.log('props.multiSelectMode: ', props.multiSelectMode)
return (
<VStack
@ -641,13 +701,14 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
updateLayout={updateLayout}
searchTerm={props.searchTerm}
applySearchQuery={(searchQuery: string) => {
console.log('searching with searchQuery: ', searchQuery)
props.applySearchQuery(searchQuery)
}}
showFilterMenu={showFilterMenu}
setShowFilterMenu={setShowFilterMenu}
inMultiSelect={inMultiSelect}
setInMultiSelect={setInMultiSelect}
multiSelectMode={props.multiSelectMode}
setMultiSelectMode={props.setMultiSelectMode}
numItemsSelected={props.numItemsSelected}
showAddLinkModal={() => props.setShowAddLinkModal(true)}
/>
<HStack css={{ width: '100%', height: '100%' }}>
<LibraryFilterMenu
@ -673,7 +734,8 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
<LibraryItemsLayout
viewer={viewerData?.me}
layout={layout}
inMultiSelect={inMultiSelect}
inMultiSelect={props.multiSelectMode !== 'off'}
isChecked={props.itemIsChecked}
{...props}
/>
)}
@ -690,6 +752,9 @@ type LibraryItemsLayoutProps = {
layout: LayoutType
viewer?: UserBasicData
inMultiSelect: boolean
isChecked: (itemId: string) => boolean
setIsChecked: (itemId: string, set: boolean) => void
} & HomeFeedContentProps
function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
@ -749,6 +814,8 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
items={props.items}
layout={props.layout}
viewer={props.viewer}
isChecked={props.isChecked}
setIsChecked={props.setIsChecked}
gridContainerRef={props.gridContainerRef}
setShowEditTitleModal={props.setShowEditTitleModal}
setLinkToEdit={props.setLinkToEdit}
@ -811,6 +878,10 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
item={props.linkToRemove?.node}
viewer={props.viewer}
layout="GRID_LAYOUT"
inMultiSelect={false}
isChecked={false}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setIsChecked={() => {}}
// eslint-disable-next-line @typescript-eslint/no-empty-function
handleAction={() => {}}
/>
@ -879,6 +950,8 @@ type LibraryItemsProps = {
setShowRemoveLinkConfirmation: (show: true) => void
inMultiSelect: boolean
isChecked: (itemId: string) => boolean
setIsChecked: (itemId: string, set: boolean) => void
actionHandler: (
action: LinkedItemCardAction,
@ -963,6 +1036,8 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
layout={props.layout}
item={linkedItem.node}
viewer={props.viewer}
isChecked={props.isChecked(linkedItem.node.id)}
setIsChecked={props.setIsChecked}
inMultiSelect={props.inMultiSelect}
handleAction={(action: LinkedItemCardAction) => {
if (action === 'delete') {

View File

@ -67,10 +67,10 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element {
<SavedSearches {...props} />
<Subscriptions {...props} />
<Labels {...props} />
{/*
<AddLinkButton
showAddLinkModal={() => props.setShowAddLinkModal(true)}
/>
/> */}
<Box css={{ height: '250px ' }} />
</Box>
{/* This spacer pushes library content to the right of

View File

@ -29,6 +29,8 @@ import {
import { CardCheckbox } from '../../patterns/LibraryCards/LibraryCardStyles'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search'
type LibraryHeaderProps = {
layout: LayoutType
updateLayout: (layout: LayoutType) => void
@ -39,21 +41,26 @@ type LibraryHeaderProps = {
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
inMultiSelect: boolean
setInMultiSelect: (set: boolean) => void
showAddLinkModal: () => void
numItemsSelected: number
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
}
export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
const [isScrolled, setIsScrolled] = useState(props.inMultiSelect)
const [showBackground, setShowBackground] = useState(
props.multiSelectMode !== 'off'
)
useEffect(() => {
if (window.scrollY > 5 || props.inMultiSelect) {
setIsScrolled(true)
if (window.scrollY > 5 || props.multiSelectMode != 'off') {
setShowBackground(true)
}
})
useScrollWatcher((changeset: ScrollOffsetChangeset) => {
setIsScrolled(window.scrollY > 5)
setShowBackground(window.scrollY > 5)
}, 0)
return (
@ -68,7 +75,7 @@ export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
zIndex: 5,
position: 'fixed',
height: HEADER_HEIGHT,
bg: isScrolled ? '$thBackground' : 'transparent',
bg: showBackground ? '$thBackground' : 'transparent',
'@mdDown': {
left: '0px',
right: '0',
@ -100,13 +107,13 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
},
}}
>
{/* <MagnifyingGlass size={20} color="#898989" /> */}
{/* */}
<ControlButtonBox
layout={props.layout}
updateLayout={props.updateLayout}
inMultiSelect={props.inMultiSelect}
setInMultiSelect={props.setInMultiSelect}
numItemsSelected={props.numItemsSelected}
multiSelectMode={props.multiSelectMode}
setMultiSelectMode={props.setMultiSelectMode}
showAddLinkModal={props.showAddLinkModal}
/>
</HStack>
)
@ -148,8 +155,10 @@ function SmallHeaderLayout(props: LibraryHeaderProps): JSX.Element {
layout={props.layout}
updateLayout={props.updateLayout}
setShowInlineSearch={setShowInlineSearch}
inMultiSelect={props.inMultiSelect}
setInMultiSelect={props.setInMultiSelect}
numItemsSelected={props.numItemsSelected}
multiSelectMode={props.multiSelectMode}
setMultiSelectMode={props.setMultiSelectMode}
showAddLinkModal={props.showAddLinkModal}
/>
</>
)}
@ -354,8 +363,11 @@ type ControlButtonBoxProps = {
updateLayout: (layout: LayoutType) => void
setShowInlineSearch?: (show: boolean) => void
inMultiSelect: boolean
setInMultiSelect: (set: boolean) => void
showAddLinkModal: () => void
numItemsSelected: number
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
}
function MultiSelectControlButtonBox(
@ -420,7 +432,7 @@ function MultiSelectControlButtonBox(
<Button
style="cancel"
onClick={(e) => {
props.setInMultiSelect(false)
props.setMultiSelectMode('off')
e.preventDefault()
}}
>
@ -468,14 +480,23 @@ function SearchControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
<PrimaryDropdown
showThemeSection={true}
startSelectMultiple={() => {
props.setInMultiSelect(true)
props.setMultiSelectMode('none')
}}
showAddLinkModal={props.showAddLinkModal}
/>
</>
)
}
function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
const [isChecked, setIsChecked] = useState(false)
useEffect(() => {
if (props.multiSelectMode === 'off' || props.multiSelectMode === 'none') {
setIsChecked(false)
}
}, [props.multiSelectMode])
const breakpoints =
props.layout == 'GRID_LAYOUT'
? {
@ -510,7 +531,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
<>
<HStack
alignment="center"
distribution={props.inMultiSelect ? 'center' : 'start'}
distribution={props.multiSelectMode !== 'off' ? 'center' : 'start'}
css={{
gap: '10px',
'@mdDown': {
@ -519,11 +540,24 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
...breakpoints,
}}
>
{props.inMultiSelect && (
<SpanBox css={{ flex: 1 }}>
<Button css={{ p: '5px', gap: '5px' }}>
<CardCheckbox isChecked={false} handleChanged={() => {}} />
&nbsp;
{props.multiSelectMode !== 'off' && (
<SpanBox
css={{
flex: 1,
display: 'flex',
gap: '2px',
alignItems: 'center',
}}
>
<CardCheckbox
isChecked={isChecked}
handleChanged={() => {
const newValue = !isChecked
props.setMultiSelectMode(newValue ? 'visible' : 'none')
setIsChecked(newValue)
}}
/>
<SpanBox css={{ pt: '2px' }}>
<Dropdown
triggerElement={
<CaretDown
@ -533,18 +567,43 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
/>
}
>
<DropdownOption onSelect={() => {}} title="All Visible" />
<DropdownOption
onSelect={() => {}}
title="All Matching Search"
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('visible')
}}
title="All"
/>
<DropdownOption
onSelect={() => {
setIsChecked(true)
props.setMultiSelectMode('search')
}}
title="All matching search"
/>
</Dropdown>
</Button>
</SpanBox>
<SpanBox
css={{
paddingLeft: '10px',
fontSize: '12px',
fontWeight: '600',
fontFamily: '$inter',
}}
>
{props.numItemsSelected} selected
</SpanBox>
</SpanBox>
)}
{props.inMultiSelect ? (
{props.multiSelectMode !== 'off' ? (
<>
<MultiSelectControlButtonBox {...props} />
<MultiSelectControlButtonBox
{...props}
// setInMultiSelect={(set: boolean) => {
// setIsChecked(false)
// props.setInMultiSelect(set)
// }}
/>
<SpanBox css={{ flex: 1 }}></SpanBox>
</>
) : (
@ -586,7 +645,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
layout={props.layout}
updateLayout={props.updateLayout}
startSelectMultiple={() => {
props.setInMultiSelect(true)
props.setMultiSelectMode('none')
}}
/>
</HStack>