Skip to content

Commit

Permalink
feat: add react-virtualized to the files list (#957)
Browse files Browse the repository at this point in the history
- Use `react-virtualized` to only render the files in view
- Loading animation when listing takes more than 1s to load
- Dir sizes only show if current dir has < 100 files (fix in an incoming PR)
  • Loading branch information
fsdiogo authored Feb 25, 2019
1 parent a5ec5be commit de2d68c
Show file tree
Hide file tree
Showing 18 changed files with 41,580 additions and 152 deletions.
2 changes: 1 addition & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class App extends Component {
const { route: Page, ipfsReady, routeInfo: { url }, navbarIsOpen, connectDropTarget, isOver } = this.props

return connectDropTarget(
<div className='sans-serif' onClick={navHelper(this.props.doUpdateUrl)}>
<div className='sans-serif h-100' onClick={navHelper(this.props.doUpdateUrl)}>
{/* Tinted overlay that appears when dragging and dropping an item */}
{ isOver && <div className='w-100 h-100 top-0 left-0 absolute' style={{ background: 'rgba(99, 202, 210, 0.2)' }} /> }
<div className='flex-l' style={{ minHeight: '100vh' }}>
Expand Down
21 changes: 7 additions & 14 deletions src/bundles/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { join, dirname } from 'path'
import { createSelector } from 'redux-bundler'
import { getDownloadLink, getShareableLink, filesToStreams } from '../lib/files'
import countDirs from '../lib/count-dirs'
import ms from 'milliseconds'

const isMac = navigator.userAgent.indexOf('Mac') !== -1

Expand Down Expand Up @@ -104,6 +103,7 @@ const fetchFiles = make(actions.FETCH, async (ipfs, id, { store }) => {
// Otherwise get the directory info
const res = await ipfs.files.ls(path, { l: true }) || []
const files = []
const showStats = res.length < 100

for (const f of res) {
let file = {
Expand All @@ -112,7 +112,7 @@ const fetchFiles = make(actions.FETCH, async (ipfs, id, { store }) => {
type: f.type === 0 ? 'file' : 'directory'
}

if (file.type === 'directory') {
if (showStats && file.type === 'directory') {
file = {
...file,
...await ipfs.files.stat(file.path)
Expand Down Expand Up @@ -149,7 +149,6 @@ const defaultState = {
}

export default (opts = {}) => {
opts.staleAfter = opts.staleAfter || ms.minutes(1)
opts.baseUrl = opts.baseUrl || '/files'

return {
Expand Down Expand Up @@ -350,17 +349,6 @@ export default (opts = {}) => {
dispatch({ type: 'FILES_UPDATE_SORT', payload: { by, asc } })
},

reactFilesFetch: createSelector(
'selectFiles',
'selectFilesIsFetching',
'selectAppTime',
(files, isFetching, appTime) => {
if (!isFetching && files && appTime - files.fetched >= opts.staleAfter) {
return { actionCreator: 'doFilesFetch' }
}
}
),

selectFiles: (state) => {
const { pageContent, sorting } = state.files

Expand Down Expand Up @@ -390,6 +378,11 @@ export default (opts = {}) => {

selectFilesIsFetching: (state) => state.files.pending.some(a => a.type === actions.FETCH),

selectShowLoadingAnimation: (state) => {
const pending = state.files.pending.find(a => a.type === actions.FETCH)
return pending ? (Date.now() - pending.start) > 1000 : false
},

selectFilesSorting: (state) => state.files.sorting,

selectWriteFilesProgress: (state) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/checkbox/Checkbox.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
left: -99999px;
}

.Checkbox > input:checked ~ span:first-of-type svg {
.Checkbox input:checked ~ span:first-of-type svg {
opacity: 1;
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Checkbox = ({ className, label, disabled, checked, onChange, ...props }) =
return (
<label className={className} {...props}>
<input className='absolute' type='checkbox' checked={checked} disabled={disabled} onChange={change} />
<span className='dib v-mid br1 w1 h1 mr1'>
<span className='dib v-mid br1 w1 h1 pointer'>
<Tick className='w1 h1 o-0 fill-aqua' viewBox='25 25 50 50' />
</span>
<span className='v-mid pl2'>
Expand Down
12 changes: 8 additions & 4 deletions src/components/loading-animation/LoadingAnimation.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
.LoadingAnimation {
position: relative;
overflow: hidden;
opacity: 0.4;
background-color: #f0f6fa;
opacity: 0.2;
height: 100%;
}

.LoadingAnimation .Checkbox span {
opacity: 1 !important;
}

.LoadingAnimationSwipe::after {
Expand All @@ -24,10 +28,10 @@

@keyframes LoadingAnimationSwipe {
0% {
transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0) skew(-20deg);
}

100% {
transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0) skew(0);
}
}
60 changes: 51 additions & 9 deletions src/components/loading-animation/LoadingAnimation.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,58 @@
import React from 'react'
import { translate } from 'react-i18next'
// Components
import GlyphDots from '../../icons/GlyphDots'
import Checkbox from '../../components/checkbox/Checkbox'
import FileIcon from '../../files/file-icon/FileIcon'
// Styles
import './LoadingAnimation.css'

const LoadingAnimation = ({ loading, children }) => {
if (!loading) return children
const FakeHeader = ({ t }) => (
<header className='gray pv2 flex items-center flex-none'>
<div className='pa2 w2'><Checkbox disabled /></div>
<div className='ph2 f6 flex-auto'>{t('fileName')}</div>
<div className='pl2 pr4 tr f6 flex-none dn db-l'>{t('size')}</div>
<div className='pa2' style={{ width: '2.5rem' }} />
</header>
)

return (
<div className='LoadingAnimation'>
<div className='LoadingAnimationSwipe'>
{ children }
const FakeFile = ({ nameWidth }) => (
<div className='b--light-gray relative flex items-center bt' style={{ height: 55 }}>
<div className='pa2 w2'>
<Checkbox disabled />
</div>
<div className='relative pointer flex items-center flex-grow-1 ph2 pv1 w-40'>
<div className='dib flex-shrink-0 mr2'>
<FileIcon cls='fill-charcoal' />
</div>
<div className='w-100'>
<div className={`w-${nameWidth} br1 bg-charcoal-muted f7`}>&nbsp;</div>
<div className='w-80 br1 mt1 bg-gray f7 o-70'>&nbsp;</div>
</div>
</div>
)
}
<div className='size mr4 pl2 pr4 flex-none dn db-l br1 tr f7 bg-gray'>&nbsp;</div>
<div className='ph2' style={{ width: '2.5rem' }}>
<GlyphDots className='fill-gray-muted pointer' />
</div>
</div>
)

const LoadingAnimation = ({ t }) => (
<div className='LoadingAnimation'>
<div className='LoadingAnimationSwipe'>
<FakeHeader t={t} />
<FakeFile nameWidth={50} />
<FakeFile nameWidth={40} />
<FakeFile nameWidth={60} />
<FakeFile nameWidth={30} />
<FakeFile nameWidth={50} />
<FakeFile nameWidth={60} />
<FakeFile nameWidth={70} />
<FakeFile nameWidth={40} />
<FakeFile nameWidth={30} />
<FakeFile nameWidth={50} />
</div>
</div>
)

export default LoadingAnimation
export default translate('files')(LoadingAnimation)
12 changes: 7 additions & 5 deletions src/files/context-menu/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import StrokeDownload from '../../icons/StrokeDownload'
class ContextMenu extends React.Component {
static propTypes = {
isOpen: PropTypes.bool,
isUpperDir: PropTypes.bool,
handleClick: PropTypes.func,
translateX: PropTypes.number,
translateY: PropTypes.number,
Expand All @@ -32,6 +33,7 @@ class ContextMenu extends React.Component {

static defaultProps = {
isOpen: false,
isUpperDir: false,
top: 0,
left: 0,
right: 'auto',
Expand All @@ -51,7 +53,7 @@ class ContextMenu extends React.Component {
}

render () {
const { t, onRename, onDelete, onDownload, onInspect, onShare, translateX, translateY, className, showDots } = this.props
const { t, onRename, onDelete, onDownload, onInspect, onShare, translateX, translateY, className, showDots, isUpperDir } = this.props

return (
<Dropdown className={className}>
Expand All @@ -64,19 +66,19 @@ class ContextMenu extends React.Component {
translateY={-translateY}
open={this.props.isOpen}
onDismiss={this.props.handleClick}>
{ onDelete &&
{ !isUpperDir && onDelete &&
<Option onClick={this.wrap('onDelete')}>
<StrokeTrash className='w2 mr2 fill-aqua' />
{t('actions.delete')}
</Option>
}
{ onRename &&
{ !isUpperDir && onRename &&
<Option onClick={this.wrap('onRename')}>
<StrokePencil className='w2 mr2 fill-aqua' />
{t('actions.rename')}
</Option>
}
{ onDownload &&
{ !isUpperDir && onDownload &&
<Option onClick={this.wrap('onDownload')}>
<StrokeDownload className='w2 mr2 fill-aqua' />
{t('actions.download')}
Expand All @@ -94,7 +96,7 @@ class ContextMenu extends React.Component {
{t('actions.copyHash')}
</Option>
</CopyToClipboard>
{ onShare &&
{ !isUpperDir && onShare &&
<Option onClick={this.wrap('onShare')}>
<StrokeShare className='w2 mr2 fill-aqua' />
{t('actions.share')}
Expand Down
16 changes: 8 additions & 8 deletions src/files/file-icon/FileIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ import DocText from '../../icons/GlyphDocText'

const style = { width: 36 }

export default function FileIcon ({ name, type }) {
export default function FileIcon ({ name, type, cls = '' }) {
if (type === 'directory') {
return <Folder className=' fill-aqua' style={style} />
return <Folder className={`fill-aqua ${cls}`} style={style} />
}

switch (typeFromExt(name)) {
case 'audio':
return <DocMusic className='fill-aqua' style={style} />
return <DocMusic className={`fill-aqua ${cls}`} style={style} />
case 'calc':
return <DocCalc className='fill-aqua' style={style} />
return <DocCalc className={`fill-aqua ${cls}`} style={style} />
case 'video':
return <DocMovie className='fill-aqua' style={style} />
return <DocMovie className={`fill-aqua ${cls}`} style={style} />
case 'text':
return <DocText className='fill-aqua' style={style} />
return <DocText className={`fill-aqua ${cls}`} style={style} />
case 'image':
return <DocPicture className='fill-aqua' style={style} />
return <DocPicture className={`fill-aqua ${cls}`} style={style} />
default:
return <Doc className='fill-aqua' style={style} />
return <Doc className={`fill-aqua ${cls}`} style={style} />
}
}
17 changes: 14 additions & 3 deletions src/files/file/File.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { join, basename } from 'path'
import filesize from 'filesize'
import classnames from 'classnames'
// React DnD
import { DropTarget, DragSource } from 'react-dnd'
import { NativeTypes } from 'react-dnd-html5-backend'
Expand Down Expand Up @@ -59,7 +60,7 @@ class File extends React.Component {
styles = {}
} = this.props

let className = 'File b--light-gray hide-child-l relative flex items-center bt'
let className = 'File b--light-gray relative flex items-center bt'

if (selected) {
className += ' selected'
Expand All @@ -78,13 +79,23 @@ class File extends React.Component {
styles.borderTop = '1px solid #eee'
}

size = filesize(cumulativeSize || size, { round: 0 })
styles.height = 55
styles.overflow = 'hidden'

size = (type === 'directory' && !cumulativeSize)
? '―'
: filesize(cumulativeSize || size, { round: 0 })

const select = (select) => onSelect(name, select)

const checkBoxCls = classnames({
'o-70 glow': !cantSelect,
'o-1': selected || focused
}, ['pl2 w2'])

const element = connectDropTarget(
<div className={className} style={styles} onContextMenu={this.handleCtxRightClick}>
<div className='child float-on-left-l pa2 w2' style={(selected || focused) ? { opacity: '1' } : null}>
<div className={checkBoxCls}>
<Checkbox disabled={cantSelect} checked={selected} onChange={select} />
</div>
{connectDragPreview(
Expand Down
Loading

0 comments on commit de2d68c

Please sign in to comment.