New "browse" UI
Also, separated out browse, ?meta, and ?module request handlers. Fixes #82
This commit is contained in:
212
modules/client/browse/FileViewer.js
Normal file
212
modules/client/browse/FileViewer.js
Normal file
@ -0,0 +1,212 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from '@emotion/core';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { formatBytes } from '../utils/format.js';
|
||||
import { createHTML } from '../utils/markup.js';
|
||||
|
||||
import { usePackageInfo } from './PackageInfo.js';
|
||||
|
||||
function getBasename(path) {
|
||||
const segments = path.split('/');
|
||||
return segments[segments.length - 1];
|
||||
}
|
||||
|
||||
function ImageViewer({ path, uri }) {
|
||||
return (
|
||||
<div css={{ padding: 20, textAlign: 'center' }}>
|
||||
<img title={getBasename(path)} src={uri} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeListing({ highlights }) {
|
||||
const lines = highlights.slice(0);
|
||||
const hasTrailingNewline = lines.length && lines[lines.length - 1] === '';
|
||||
if (hasTrailingNewline) {
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="code-listing"
|
||||
css={{
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5
|
||||
}}
|
||||
>
|
||||
<table
|
||||
css={{
|
||||
border: 'none',
|
||||
borderCollapse: 'collapse',
|
||||
borderSpacing: 0
|
||||
}}
|
||||
>
|
||||
<tbody>
|
||||
{lines.map((line, index) => {
|
||||
const lineNumber = index + 1;
|
||||
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td
|
||||
id={`L${lineNumber}`}
|
||||
css={{
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
color: 'rgba(27,31,35,.3)',
|
||||
textAlign: 'right',
|
||||
verticalAlign: 'top',
|
||||
width: '1%',
|
||||
minWidth: 50,
|
||||
userSelect: 'none'
|
||||
}}
|
||||
>
|
||||
<span>{lineNumber}</span>
|
||||
</td>
|
||||
<td
|
||||
id={`LC${lineNumber}`}
|
||||
css={{
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
color: '#24292e',
|
||||
whiteSpace: 'pre'
|
||||
}}
|
||||
>
|
||||
<code dangerouslySetInnerHTML={createHTML(line)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{!hasTrailingNewline && (
|
||||
<tr key="no-newline">
|
||||
<td
|
||||
css={{
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
color: 'rgba(27,31,35,.3)',
|
||||
textAlign: 'right',
|
||||
verticalAlign: 'top',
|
||||
width: '1%',
|
||||
minWidth: 50,
|
||||
userSelect: 'none'
|
||||
}}
|
||||
>
|
||||
\
|
||||
</td>
|
||||
<td
|
||||
css={{
|
||||
paddingLeft: 10,
|
||||
color: 'rgba(27,31,35,.3)',
|
||||
userSelect: 'none'
|
||||
}}
|
||||
>
|
||||
No newline at end of file
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BinaryViewer() {
|
||||
return (
|
||||
<div css={{ padding: 20 }}>
|
||||
<p css={{ textAlign: 'center' }}>No preview available.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FileViewer({ path, details }) {
|
||||
const { packageName, packageVersion } = usePackageInfo();
|
||||
const { highlights, uri, language, size } = details;
|
||||
|
||||
const segments = path.split('/');
|
||||
const filename = segments[segments.length - 1];
|
||||
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
border: '1px solid #dfe2e5',
|
||||
borderRadius: 3,
|
||||
'@media (max-width: 700px)': {
|
||||
borderRightWidth: 0,
|
||||
borderLeftWidth: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
padding: 10,
|
||||
background: '#f6f8fa',
|
||||
color: '#424242',
|
||||
border: '1px solid #d1d5da',
|
||||
borderTopLeftRadius: 3,
|
||||
borderTopRightRadius: 3,
|
||||
margin: '-1px -1px 0',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
'@media (max-width: 700px)': {
|
||||
paddingRight: 20,
|
||||
paddingLeft: 20
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>{formatBytes(size)}</span> <span>{language}</span>{' '}
|
||||
<a
|
||||
title={filename}
|
||||
href={`/${packageName}@${packageVersion}${path}`}
|
||||
css={{
|
||||
display: 'inline-block',
|
||||
textDecoration: 'none',
|
||||
padding: '2px 8px',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.9rem',
|
||||
color: '#24292e',
|
||||
backgroundColor: '#eff3f6',
|
||||
border: '1px solid rgba(27,31,35,.2)',
|
||||
borderRadius: 3,
|
||||
':hover': {
|
||||
backgroundColor: '#e6ebf1',
|
||||
borderColor: 'rgba(27,31,35,.35)'
|
||||
},
|
||||
':active': {
|
||||
backgroundColor: '#e9ecef',
|
||||
borderColor: 'rgba(27,31,35,.35)',
|
||||
boxShadow: 'inset 0 0.15em 0.3em rgba(27,31,35,.15)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
View Raw
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{highlights ? (
|
||||
<CodeListing highlights={highlights} />
|
||||
) : uri ? (
|
||||
<ImageViewer path={path} uri={uri} />
|
||||
) : (
|
||||
<BinaryViewer />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
FileViewer.propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
details: PropTypes.shape({
|
||||
contentType: PropTypes.string.isRequired,
|
||||
highlights: PropTypes.arrayOf(PropTypes.string), // code
|
||||
uri: PropTypes.string, // images
|
||||
integrity: PropTypes.string.isRequired,
|
||||
language: PropTypes.string.isRequired,
|
||||
size: PropTypes.number.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user