unpkg/modules/client/browse/FolderViewer.js

199 lines
4.6 KiB
JavaScript

/** @jsx jsx */
import { jsx } from '@emotion/core';
import PropTypes from 'prop-types';
import VisuallyHidden from '@reach/visually-hidden';
import sortBy from 'sort-by';
import { formatBytes } from '../utils/format.js';
import { ContentArea, ContentAreaHeaderBar } from './ContentArea.js';
import { FolderIcon, FileIcon, FileCodeIcon } from './Icons.js';
const linkStyle = {
color: '#0076ff',
textDecoration: 'none',
':hover': {
textDecoration: 'underline'
}
};
const tableCellStyle = {
paddingTop: 6,
paddingRight: 3,
paddingBottom: 6,
paddingLeft: 3,
borderTop: '1px solid #eaecef'
};
const iconCellStyle = {
...tableCellStyle,
color: '#424242',
width: 17,
paddingRight: 2,
paddingLeft: 10,
'@media (max-width: 700px)': {
paddingLeft: 20
}
};
const typeCellStyle = {
...tableCellStyle,
textAlign: 'right',
paddingRight: 10,
'@media (max-width: 700px)': {
paddingRight: 20
}
};
function getRelName(path, base) {
return path.substr(base.length > 1 ? base.length + 1 : 1);
}
export default function FolderViewer({ path, details: entries }) {
const { subdirs, files } = Object.keys(entries).reduce(
(memo, key) => {
const { subdirs, files } = memo;
const entry = entries[key];
if (entry.type === 'directory') {
subdirs.push(entry);
} else if (entry.type === 'file') {
files.push(entry);
}
return memo;
},
{ subdirs: [], files: [] }
);
subdirs.sort(sortBy('path'));
files.sort(sortBy('path'));
const rows = [];
if (path !== '/') {
rows.push(
<tr key="..">
<td css={iconCellStyle} />
<td css={tableCellStyle}>
<a title="Parent directory" href="../" css={linkStyle}>
..
</a>
</td>
<td css={tableCellStyle}></td>
<td css={typeCellStyle}></td>
</tr>
);
}
subdirs.forEach(({ path: dirname }) => {
const relName = getRelName(dirname, path);
const href = relName + '/';
rows.push(
<tr key={relName}>
<td css={iconCellStyle}>
<FolderIcon />
</td>
<td css={tableCellStyle}>
<a title={relName} href={href} css={linkStyle}>
{relName}
</a>
</td>
<td css={tableCellStyle}>-</td>
<td css={typeCellStyle}>-</td>
</tr>
);
});
files.forEach(({ path: filename, size, contentType }) => {
const relName = getRelName(filename, path);
const href = relName;
rows.push(
<tr key={relName}>
<td css={iconCellStyle}>
{contentType === 'text/plain' || contentType === 'text/markdown' ? (
<FileIcon />
) : (
<FileCodeIcon />
)}
</td>
<td css={tableCellStyle}>
<a title={relName} href={href} css={linkStyle}>
{relName}
</a>
</td>
<td css={tableCellStyle}>{formatBytes(size)}</td>
<td css={typeCellStyle}>{contentType}</td>
</tr>
);
});
let counts = [];
if (files.length > 0) {
counts.push(`${files.length} file${files.length === 1 ? '' : 's'}`);
}
if (subdirs.length > 0) {
counts.push(`${subdirs.length} folder${subdirs.length === 1 ? '' : 's'}`);
}
return (
<ContentArea>
<ContentAreaHeaderBar>
<span>{counts.join(', ')}</span>
</ContentAreaHeaderBar>
<table
css={{
width: '100%',
borderCollapse: 'collapse',
borderRadius: 2,
background: '#fff',
'@media (max-width: 700px)': {
'& th + th + th + th, & td + td + td + td': {
display: 'none'
}
},
'& tr:first-of-type td': {
borderTop: 0
}
}}
>
<thead>
<tr>
<th>
<VisuallyHidden>Icon</VisuallyHidden>
</th>
<th>
<VisuallyHidden>Name</VisuallyHidden>
</th>
<th>
<VisuallyHidden>Size</VisuallyHidden>
</th>
<th>
<VisuallyHidden>Content Type</VisuallyHidden>
</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
</ContentArea>
);
}
if (process.env.NODE_ENV !== 'production') {
FolderViewer.propTypes = {
path: PropTypes.string.isRequired,
details: PropTypes.objectOf(
PropTypes.shape({
path: PropTypes.string.isRequired,
type: PropTypes.oneOf(['directory', 'file']).isRequired,
contentType: PropTypes.string, // file only
integrity: PropTypes.string, // file only
size: PropTypes.number // file only
})
).isRequired
};
}