refactor(dev-only): file list item style
This commit is contained in:
@@ -13,6 +13,7 @@ import { NotesController } from '@/Controllers/NotesController/NotesController'
|
|||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import { classNames } from '@standardnotes/utils'
|
import { classNames } from '@standardnotes/utils'
|
||||||
import { ContentType, SNTag } from '@standardnotes/snjs'
|
import { ContentType, SNTag } from '@standardnotes/snjs'
|
||||||
|
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -97,6 +98,8 @@ const ContentList: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
const hasNotes = items.some((item) => item.content_type === ContentType.Note)
|
const hasNotes = items.some((item) => item.content_type === ContentType.Note)
|
||||||
|
|
||||||
|
const isFilesTableViewEnabled = featureTrunkEnabled(FeatureTrunkName.FilesTableView)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -132,8 +135,8 @@ const ContentList: FunctionComponent<Props> = ({
|
|||||||
onSelect={selectItem}
|
onSelect={selectItem}
|
||||||
tags={getTagsForItem(item)}
|
tags={getTagsForItem(item)}
|
||||||
notesController={notesController}
|
notesController={notesController}
|
||||||
isPreviousItemTiled={previousItem?.content_type === ContentType.File}
|
isPreviousItemTiled={previousItem?.content_type === ContentType.File && !isFilesTableViewEnabled}
|
||||||
isNextItemTiled={nextItem?.content_type === ContentType.File}
|
isNextItemTiled={nextItem?.content_type === ContentType.File && !isFilesTableViewEnabled}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
|
import { featureTrunkEnabled, FeatureTrunkName } from '@/FeatureTrunk'
|
||||||
import { ContentType, FileItem, SNNote } from '@standardnotes/snjs'
|
import { ContentType, FileItem, SNNote } from '@standardnotes/snjs'
|
||||||
import React, { FunctionComponent } from 'react'
|
import React, { FunctionComponent } from 'react'
|
||||||
import FileListItem from './FileListItem'
|
import FileListItem from './FileListItem'
|
||||||
|
import FileListItemCard from './FileListItemCard'
|
||||||
import NoteListItem from './NoteListItem'
|
import NoteListItem from './NoteListItem'
|
||||||
import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps'
|
import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps'
|
||||||
import { ListableContentItem } from './Types/ListableContentItem'
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
|
|
||||||
const ContentListItem: FunctionComponent<AbstractListItemProps<ListableContentItem>> = (props) => {
|
const ContentListItem: FunctionComponent<AbstractListItemProps<ListableContentItem>> = (props) => {
|
||||||
|
const isFilesTableViewEnabled = featureTrunkEnabled(FeatureTrunkName.FilesTableView)
|
||||||
|
|
||||||
switch (props.item.content_type) {
|
switch (props.item.content_type) {
|
||||||
case ContentType.Note:
|
case ContentType.Note:
|
||||||
return <NoteListItem {...props} item={props.item as SNNote} />
|
return <NoteListItem {...props} item={props.item as SNNote} />
|
||||||
case ContentType.File:
|
case ContentType.File: {
|
||||||
return <FileListItem {...props} item={props.item as FileItem} />
|
if (isFilesTableViewEnabled) {
|
||||||
|
return <FileListItem {...props} item={props.item as FileItem} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FileListItemCard {...props} item={props.item as FileItem} />
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
|||||||
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||||
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
|
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
|
||||||
import { classNames } from '@standardnotes/utils'
|
import { classNames } from '@standardnotes/utils'
|
||||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
|
||||||
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
||||||
import { useApplication } from '../ApplicationProvider'
|
import { useApplication } from '../ApplicationProvider'
|
||||||
import Icon from '../Icon/Icon'
|
|
||||||
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
||||||
|
import ListItemFlagIcons from './ListItemFlagIcons'
|
||||||
|
|
||||||
const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
const FileListItemCard: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
||||||
filesController,
|
filesController,
|
||||||
hideDate,
|
hideDate,
|
||||||
hideIcon,
|
hideIcon,
|
||||||
@@ -75,57 +74,38 @@ const FileListItem: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
|||||||
}, [file, onSelect, setPaneLayout])
|
}, [file, onSelect, setPaneLayout])
|
||||||
|
|
||||||
const IconComponent = () =>
|
const IconComponent = () =>
|
||||||
getFileIconComponent(getIconForFileType((file as FileItem).mimeType), 'w-10 h-10 flex-shrink-0')
|
getFileIconComponent(getIconForFileType((file as FileItem).mimeType), 'w-5 h-5 flex-shrink-0')
|
||||||
|
|
||||||
useContextMenuEvent(listItemRef, openContextMenu)
|
useContextMenuEvent(listItemRef, openContextMenu)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={listItemRef}
|
ref={listItemRef}
|
||||||
className={classNames('flex max-h-[300px] w-[190px] cursor-pointer px-1 pt-2 text-text md:w-[200px]')}
|
className={classNames(
|
||||||
|
'content-list-item flex w-full cursor-pointer items-stretch text-text',
|
||||||
|
selected && 'selected border-l-2px border-solid border-info',
|
||||||
|
)}
|
||||||
id={file.uuid}
|
id={file.uuid}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div
|
{!hideIcon ? (
|
||||||
className={`flex flex-col justify-between overflow-hidden rounded bg-passive-5 pt-5 transition-all hover:bg-passive-4 ${
|
<div className="mr-0 flex flex-col items-center justify-between p-4.5 pr-3">
|
||||||
selected ? 'border-[1px] border-solid border-info' : 'border-[1px] border-solid border-border'
|
<IconComponent />
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className={'px-5'}>
|
|
||||||
{!hideIcon ? (
|
|
||||||
<div className="mr-0">
|
|
||||||
<IconComponent />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="pr-4" />
|
|
||||||
)}
|
|
||||||
<div className="min-w-0 flex-grow py-4 px-0">
|
|
||||||
<div className="line-clamp-2 overflow-hidden text-editor font-semibold">
|
|
||||||
<div className="break-word line-clamp-2 mr-2 overflow-hidden">{file.title}</div>
|
|
||||||
</div>
|
|
||||||
<ListItemMetadata item={file} hideDate={hideDate} sortBy={sortBy} />
|
|
||||||
<ListItemTags hideTags={hideTags} tags={tags} />
|
|
||||||
<ListItemConflictIndicator item={file} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
) : (
|
||||||
className={classNames(
|
<div className="pr-4" />
|
||||||
'border-t-[1px] border-solid border-border p-3 text-xs font-bold',
|
)}
|
||||||
selected ? 'bg-info text-info-contrast' : 'bg-passive-4 text-neutral',
|
<div className="min-w-0 flex-grow border-b border-solid border-border py-4 px-0">
|
||||||
)}
|
<div className="flex items-start justify-between overflow-hidden text-base font-semibold leading-[1.3]">
|
||||||
>
|
<div className="break-word mr-2">{file.title}</div>
|
||||||
<div className="flex justify-between">
|
|
||||||
{formatSizeToReadableString(file.decryptedSize)}
|
|
||||||
{backupInfo && (
|
|
||||||
<div title="File is backed up locally">
|
|
||||||
<Icon type="check-circle" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ListItemMetadata item={file} hideDate={hideDate} sortBy={sortBy} />
|
||||||
|
<ListItemTags hideTags={hideTags} tags={tags} />
|
||||||
|
<ListItemConflictIndicator item={file} />
|
||||||
</div>
|
</div>
|
||||||
|
<ListItemFlagIcons className="p-4" item={file} isFileBackedUp={!!backupInfo} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default observer(FileListItem)
|
export default observer(FileListItemCard)
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { FileItem, FileBackupRecord } from '@standardnotes/snjs'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { getFileIconComponent } from '../FilePreview/getFileIconComponent'
|
||||||
|
import ListItemConflictIndicator from './ListItemConflictIndicator'
|
||||||
|
import ListItemTags from './ListItemTags'
|
||||||
|
import ListItemMetadata from './ListItemMetadata'
|
||||||
|
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
||||||
|
import { useResponsiveAppPane } from '../Panes/ResponsivePaneProvider'
|
||||||
|
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
|
||||||
|
import { classNames } from '@standardnotes/utils'
|
||||||
|
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||||
|
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
|
||||||
|
import { useApplication } from '../ApplicationProvider'
|
||||||
|
import Icon from '../Icon/Icon'
|
||||||
|
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
||||||
|
|
||||||
|
const FileListItemCard: FunctionComponent<DisplayableListItemProps<FileItem>> = ({
|
||||||
|
filesController,
|
||||||
|
hideDate,
|
||||||
|
hideIcon,
|
||||||
|
hideTags,
|
||||||
|
item: file,
|
||||||
|
onSelect,
|
||||||
|
selected,
|
||||||
|
sortBy,
|
||||||
|
tags,
|
||||||
|
}) => {
|
||||||
|
const { setPaneLayout } = useResponsiveAppPane()
|
||||||
|
const application = useApplication()
|
||||||
|
|
||||||
|
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void application.fileBackups?.getFileBackupInfo(file).then(setBackupInfo)
|
||||||
|
}, [application, file])
|
||||||
|
|
||||||
|
const listItemRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const openFileContextMenu = useCallback(
|
||||||
|
(posX: number, posY: number) => {
|
||||||
|
filesController.setShowFileContextMenu(false)
|
||||||
|
filesController.setFileContextMenuLocation({
|
||||||
|
x: posX,
|
||||||
|
y: posY,
|
||||||
|
})
|
||||||
|
filesController.setShowFileContextMenu(true)
|
||||||
|
},
|
||||||
|
[filesController],
|
||||||
|
)
|
||||||
|
|
||||||
|
const openContextMenu = useCallback(
|
||||||
|
async (posX: number, posY: number) => {
|
||||||
|
let shouldOpenContextMenu = selected
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
const { didSelect } = await onSelect(file)
|
||||||
|
if (didSelect) {
|
||||||
|
shouldOpenContextMenu = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldOpenContextMenu) {
|
||||||
|
openFileContextMenu(posX, posY)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selected, onSelect, file, openFileContextMenu],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onClick = useCallback(async () => {
|
||||||
|
const { didSelect } = await onSelect(file, true)
|
||||||
|
if (didSelect) {
|
||||||
|
setPaneLayout(PaneLayout.Editing)
|
||||||
|
}
|
||||||
|
}, [file, onSelect, setPaneLayout])
|
||||||
|
|
||||||
|
const IconComponent = () =>
|
||||||
|
getFileIconComponent(getIconForFileType((file as FileItem).mimeType), 'w-10 h-10 flex-shrink-0')
|
||||||
|
|
||||||
|
useContextMenuEvent(listItemRef, openContextMenu)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={listItemRef}
|
||||||
|
className={classNames('flex max-h-[300px] w-[190px] cursor-pointer px-1 pt-2 text-text md:w-[200px]')}
|
||||||
|
id={file.uuid}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex flex-col justify-between overflow-hidden rounded bg-passive-5 pt-5 transition-all hover:bg-passive-4 ${
|
||||||
|
selected ? 'border-[1px] border-solid border-info' : 'border-[1px] border-solid border-border'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={'px-5'}>
|
||||||
|
{!hideIcon ? (
|
||||||
|
<div className="mr-0">
|
||||||
|
<IconComponent />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="pr-4" />
|
||||||
|
)}
|
||||||
|
<div className="min-w-0 flex-grow py-4 px-0">
|
||||||
|
<div className="line-clamp-2 overflow-hidden text-editor font-semibold">
|
||||||
|
<div className="break-word line-clamp-2 mr-2 overflow-hidden">{file.title}</div>
|
||||||
|
</div>
|
||||||
|
<ListItemMetadata item={file} hideDate={hideDate} sortBy={sortBy} />
|
||||||
|
<ListItemTags hideTags={hideTags} tags={tags} />
|
||||||
|
<ListItemConflictIndicator item={file} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'border-t-[1px] border-solid border-border p-3 text-xs font-bold',
|
||||||
|
selected ? 'bg-info text-info-contrast' : 'bg-passive-4 text-neutral',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
{formatSizeToReadableString(file.decryptedSize)}
|
||||||
|
{backupInfo && (
|
||||||
|
<div title="File is backed up locally">
|
||||||
|
<Icon type="check-circle" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(FileListItemCard)
|
||||||
@@ -12,10 +12,17 @@ type Props = {
|
|||||||
}
|
}
|
||||||
hasFiles?: boolean
|
hasFiles?: boolean
|
||||||
hasBorder?: boolean
|
hasBorder?: boolean
|
||||||
|
isFileBackedUp?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false, hasBorder = true, className }) => {
|
const ListItemFlagIcons: FunctionComponent<Props> = ({
|
||||||
|
item,
|
||||||
|
hasFiles = false,
|
||||||
|
hasBorder = true,
|
||||||
|
isFileBackedUp = false,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-start ${hasBorder && 'border-b border-solid border-border'} ${className} pl-0`}>
|
<div className={`flex items-start ${hasBorder && 'border-b border-solid border-border'} ${className} pl-0`}>
|
||||||
{item.locked && (
|
{item.locked && (
|
||||||
@@ -48,6 +55,11 @@ const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false, h
|
|||||||
<Icon ariaLabel="Starred" type="star-filled" className="text-warning" size="medium" />
|
<Icon ariaLabel="Starred" type="star-filled" className="text-warning" size="medium" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{isFileBackedUp && (
|
||||||
|
<span className="ml-1.5 flex items-center" title="File is backed up locally">
|
||||||
|
<Icon ariaLabel="File is backed up locally" type="check-circle" className="text-info" size="medium" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user