Fixed some layout issues
This commit is contained in:
parent
7215072c72
commit
33edf5beec
|
@ -5,8 +5,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { fontSans, fontMono } from '../utils/style.js';
|
import { fontSans, fontMono } from '../utils/style.js';
|
||||||
|
|
||||||
import { PackageInfoProvider } from './PackageInfo.js';
|
import FolderViewer from './FolderViewer.js';
|
||||||
import DirectoryViewer from './DirectoryViewer.js';
|
|
||||||
import FileViewer from './FileViewer.js';
|
import FileViewer from './FileViewer.js';
|
||||||
import { TwitterIcon, GitHubIcon } from './Icons.js';
|
import { TwitterIcon, GitHubIcon } from './Icons.js';
|
||||||
|
|
||||||
|
@ -128,30 +127,52 @@ function Link({ css, ...rest }) {
|
||||||
css={{
|
css={{
|
||||||
color: '#0076ff',
|
color: '#0076ff',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
':hover': {
|
':hover': { textDecoration: 'underline' },
|
||||||
textDecoration: 'underline'
|
|
||||||
},
|
|
||||||
...css
|
...css
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App({
|
function AppHeader() {
|
||||||
|
return (
|
||||||
|
<header css={{ marginTop: '2rem' }}>
|
||||||
|
<h1
|
||||||
|
css={{
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '3rem',
|
||||||
|
letterSpacing: '0.05em'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a href="/" css={{ color: '#000', textDecoration: 'none' }}>
|
||||||
|
UNPKG
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
{/*
|
||||||
|
<nav>
|
||||||
|
<Link href="#" css={{ color: '#c400ff' }}>
|
||||||
|
Become a Sponsor
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
*/}
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppNavigation({
|
||||||
packageName,
|
packageName,
|
||||||
packageVersion,
|
packageVersion,
|
||||||
availableVersions = [],
|
availableVersions,
|
||||||
filename,
|
filename
|
||||||
target
|
|
||||||
}) {
|
}) {
|
||||||
function handleChange(event) {
|
function handleVersionChange(nextVersion) {
|
||||||
window.location.href = window.location.href.replace(
|
window.location.href = window.location.href.replace(
|
||||||
'@' + packageVersion,
|
'@' + packageVersion,
|
||||||
'@' + event.target.value
|
'@' + nextVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const breadcrumbs = [];
|
let breadcrumbs = [];
|
||||||
|
|
||||||
if (filename === '/') {
|
if (filename === '/') {
|
||||||
breadcrumbs.push(packageName);
|
breadcrumbs.push(packageName);
|
||||||
|
@ -160,12 +181,11 @@ export default function App({
|
||||||
|
|
||||||
breadcrumbs.push(<Link href={`${url}/`}>{packageName}</Link>);
|
breadcrumbs.push(<Link href={`${url}/`}>{packageName}</Link>);
|
||||||
|
|
||||||
const segments = filename
|
let segments = filename
|
||||||
.replace(/^\/+/, '')
|
.replace(/^\/+/, '')
|
||||||
.replace(/\/+$/, '')
|
.replace(/\/+$/, '')
|
||||||
.split('/');
|
.split('/');
|
||||||
|
let lastSegment = segments.pop();
|
||||||
const lastSegment = segments.pop();
|
|
||||||
|
|
||||||
segments.forEach(segment => {
|
segments.forEach(segment => {
|
||||||
url += `/${segment}`;
|
url += `/${segment}`;
|
||||||
|
@ -175,8 +195,126 @@ export default function App({
|
||||||
breadcrumbs.push(lastSegment);
|
breadcrumbs.push(lastSegment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Provide a user pref to go full width?
|
return (
|
||||||
const maxContentWidth = 940;
|
<header
|
||||||
|
css={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
'@media (max-width: 700px)': {
|
||||||
|
flexDirection: 'column-reverse',
|
||||||
|
alignItems: 'flex-start'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
css={{
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
flex: 1,
|
||||||
|
wordBreak: 'break-all'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<nav>
|
||||||
|
{breadcrumbs.map((item, index, array) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
{index !== 0 && (
|
||||||
|
<span css={{ paddingLeft: 5, paddingRight: 5 }}>/</span>
|
||||||
|
)}
|
||||||
|
{index === array.length - 1 ? <strong>{item}</strong> : item}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</h1>
|
||||||
|
<PackageVersionPicker
|
||||||
|
packageVersion={packageVersion}
|
||||||
|
availableVersions={availableVersions}
|
||||||
|
onChange={handleVersionChange}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PackageVersionPicker({ packageVersion, availableVersions, onChange }) {
|
||||||
|
function handleChange(event) {
|
||||||
|
if (onChange) onChange(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
css={{
|
||||||
|
marginLeft: 20,
|
||||||
|
'@media (max-width: 700px)': {
|
||||||
|
marginLeft: 0,
|
||||||
|
marginBottom: 0
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
Version:{' '}
|
||||||
|
<select
|
||||||
|
name="version"
|
||||||
|
defaultValue={packageVersion}
|
||||||
|
onChange={handleChange}
|
||||||
|
css={{
|
||||||
|
appearance: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '4px 24px 4px 8px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '0.9em',
|
||||||
|
color: '#24292e',
|
||||||
|
border: '1px solid rgba(27,31,35,.2)',
|
||||||
|
borderRadius: 3,
|
||||||
|
backgroundColor: '#eff3f6',
|
||||||
|
backgroundImage: `url(${SelectDownArrow})`,
|
||||||
|
backgroundPosition: 'right 8px center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundSize: 'auto 25%',
|
||||||
|
':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)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{availableVersions.map(v => (
|
||||||
|
<option key={v} value={v}>
|
||||||
|
{v}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppContent({ packageName, packageVersion, target }) {
|
||||||
|
return target.type === 'directory' ? (
|
||||||
|
<FolderViewer path={target.path} details={target.details} />
|
||||||
|
) : target.type === 'file' ? (
|
||||||
|
<FileViewer
|
||||||
|
packageName={packageName}
|
||||||
|
packageVersion={packageVersion}
|
||||||
|
path={target.path}
|
||||||
|
details={target.details}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App({
|
||||||
|
packageName,
|
||||||
|
packageVersion,
|
||||||
|
availableVersions = [],
|
||||||
|
filename,
|
||||||
|
target
|
||||||
|
}) {
|
||||||
|
let maxContentWidth = 940;
|
||||||
|
// TODO: Make this changeable
|
||||||
|
let isFullWidth = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -191,115 +329,25 @@ export default function App({
|
||||||
margin: '0 auto'
|
margin: '0 auto'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<header css={{ marginTop: '2rem' }}>
|
<AppHeader />
|
||||||
<h1
|
|
||||||
css={{
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: '3rem',
|
|
||||||
letterSpacing: '0.05em'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<a href="/" css={{ color: '#000', textDecoration: 'none' }}>
|
|
||||||
UNPKG
|
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
{/*
|
|
||||||
<nav>
|
|
||||||
<Link href="#" css={{ color: '#c400ff' }}>
|
|
||||||
Become a Sponsor
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
*/}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<header
|
|
||||||
css={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
'@media (max-width: 700px)': {
|
|
||||||
flexDirection: 'column-reverse',
|
|
||||||
alignItems: 'flex-start'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1
|
|
||||||
css={{
|
|
||||||
fontSize: '1.5rem',
|
|
||||||
fontWeight: 'normal',
|
|
||||||
flex: 1,
|
|
||||||
wordBreak: 'break-all'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<nav>
|
|
||||||
{breadcrumbs.map((item, index, array) => (
|
|
||||||
<Fragment key={index}>
|
|
||||||
{index !== 0 && (
|
|
||||||
<span css={{ paddingLeft: 5, paddingRight: 5 }}>/</span>
|
|
||||||
)}
|
|
||||||
{index === array.length - 1 ? (
|
|
||||||
<strong>{item}</strong>
|
|
||||||
) : (
|
|
||||||
item
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
</h1>
|
|
||||||
<p
|
|
||||||
css={{
|
|
||||||
marginLeft: 20,
|
|
||||||
'@media (max-width: 700px)': {
|
|
||||||
marginLeft: 0,
|
|
||||||
marginBottom: 0
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<label>
|
|
||||||
Version:{' '}
|
|
||||||
<select
|
|
||||||
name="version"
|
|
||||||
defaultValue={packageVersion}
|
|
||||||
onChange={handleChange}
|
|
||||||
css={{
|
|
||||||
appearance: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: '4px 24px 4px 8px',
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '0.9em',
|
|
||||||
color: '#24292e',
|
|
||||||
border: '1px solid rgba(27,31,35,.2)',
|
|
||||||
borderRadius: 3,
|
|
||||||
backgroundColor: '#eff3f6',
|
|
||||||
backgroundImage: `url(${SelectDownArrow})`,
|
|
||||||
backgroundPosition: 'right 8px center',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
backgroundSize: 'auto 25%',
|
|
||||||
':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)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{availableVersions.map(v => (
|
|
||||||
<option key={v} value={v}>
|
|
||||||
{v}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
css={{
|
css={{
|
||||||
maxWidth: maxContentWidth,
|
maxWidth: isFullWidth ? undefined : maxContentWidth,
|
||||||
|
padding: '0 20px',
|
||||||
|
margin: '0 auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppNavigation
|
||||||
|
packageName={packageName}
|
||||||
|
packageVersion={packageVersion}
|
||||||
|
availableVersions={availableVersions}
|
||||||
|
filename={filename}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
maxWidth: isFullWidth ? undefined : maxContentWidth,
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
'@media (max-width: 700px)': {
|
'@media (max-width: 700px)': {
|
||||||
|
@ -308,16 +356,11 @@ export default function App({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PackageInfoProvider
|
<AppContent
|
||||||
packageName={packageName}
|
packageName={packageName}
|
||||||
packageVersion={packageVersion}
|
packageVersion={packageVersion}
|
||||||
>
|
target={target}
|
||||||
{target.type === 'directory' ? (
|
/>
|
||||||
<DirectoryViewer path={target.path} details={target.details} />
|
|
||||||
) : target.type === 'file' ? (
|
|
||||||
<FileViewer path={target.path} details={target.details} />
|
|
||||||
) : null}
|
|
||||||
</PackageInfoProvider>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -342,7 +385,6 @@ export default function App({
|
||||||
<p>© {new Date().getFullYear()} UNPKG</p>
|
<p>© {new Date().getFullYear()} UNPKG</p>
|
||||||
<p css={{ fontSize: '1.5rem' }}>
|
<p css={{ fontSize: '1.5rem' }}>
|
||||||
<a
|
<a
|
||||||
title="Twitter"
|
|
||||||
href="https://twitter.com/unpkg"
|
href="https://twitter.com/unpkg"
|
||||||
css={{
|
css={{
|
||||||
color: '#aaa',
|
color: '#aaa',
|
||||||
|
@ -353,13 +395,12 @@ export default function App({
|
||||||
<TwitterIcon />
|
<TwitterIcon />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
title="GitHub"
|
|
||||||
href="https://github.com/mjackson/unpkg"
|
href="https://github.com/mjackson/unpkg"
|
||||||
css={{
|
css={{
|
||||||
color: '#aaa',
|
color: '#aaa',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
marginLeft: '1rem',
|
':hover': { color: 'white' },
|
||||||
':hover': { color: 'white' }
|
marginLeft: '1rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GitHubIcon />
|
<GitHubIcon />
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/** @jsx jsx */
|
||||||
|
import { jsx } from '@emotion/core';
|
||||||
|
|
||||||
|
const maxWidth = 700;
|
||||||
|
|
||||||
|
export function ContentArea({ children, css }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={{
|
||||||
|
border: '1px solid #dfe2e5',
|
||||||
|
borderRadius: 3,
|
||||||
|
[`@media (max-width: ${maxWidth}px)`]: {
|
||||||
|
borderRightWidth: 0,
|
||||||
|
borderLeftWidth: 0
|
||||||
|
},
|
||||||
|
...css
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContentAreaHeaderBar({ children, css }) {
|
||||||
|
return (
|
||||||
|
<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: ${maxWidth}px)`]: {
|
||||||
|
paddingRight: 20,
|
||||||
|
paddingLeft: 20
|
||||||
|
},
|
||||||
|
...css
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,10 +5,10 @@ import PropTypes from 'prop-types';
|
||||||
import { formatBytes } from '../utils/format.js';
|
import { formatBytes } from '../utils/format.js';
|
||||||
import { createHTML } from '../utils/markup.js';
|
import { createHTML } from '../utils/markup.js';
|
||||||
|
|
||||||
import { usePackageInfo } from './PackageInfo.js';
|
import { ContentArea, ContentAreaHeaderBar } from './ContentArea.js';
|
||||||
|
|
||||||
function getBasename(path) {
|
function getBasename(path) {
|
||||||
const segments = path.split('/');
|
let segments = path.split('/');
|
||||||
return segments[segments.length - 1];
|
return segments[segments.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@ function ImageViewer({ path, uri }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function CodeListing({ highlights }) {
|
function CodeListing({ highlights }) {
|
||||||
const lines = highlights.slice(0);
|
let lines = highlights.slice(0);
|
||||||
const hasTrailingNewline = lines.length && lines[lines.length - 1] === '';
|
let hasTrailingNewline = lines.length && lines[lines.length - 1] === '';
|
||||||
if (hasTrailingNewline) {
|
if (hasTrailingNewline) {
|
||||||
lines.pop();
|
lines.pop();
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ function CodeListing({ highlights }) {
|
||||||
>
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
{lines.map((line, index) => {
|
{lines.map((line, index) => {
|
||||||
const lineNumber = index + 1;
|
let lineNumber = index + 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
|
@ -120,71 +120,48 @@ function BinaryViewer() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FileViewer({ path, details }) {
|
export default function FileViewer({
|
||||||
const { packageName, packageVersion } = usePackageInfo();
|
packageName,
|
||||||
const { highlights, uri, language, size } = details;
|
packageVersion,
|
||||||
|
path,
|
||||||
const segments = path.split('/');
|
details
|
||||||
const filename = segments[segments.length - 1];
|
}) {
|
||||||
|
let { highlights, uri, language, size } = details;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<ContentArea>
|
||||||
css={{
|
<ContentAreaHeaderBar>
|
||||||
border: '1px solid #dfe2e5',
|
<span>{formatBytes(size)}</span>
|
||||||
borderRadius: 3,
|
<span>{language}</span>
|
||||||
'@media (max-width: 700px)': {
|
<span>
|
||||||
borderRightWidth: 0,
|
<a
|
||||||
borderLeftWidth: 0
|
href={`/${packageName}@${packageVersion}${path}`}
|
||||||
}
|
css={{
|
||||||
}}
|
display: 'inline-block',
|
||||||
>
|
marginLeft: 8,
|
||||||
<div
|
padding: '2px 8px',
|
||||||
css={{
|
textDecoration: 'none',
|
||||||
padding: 10,
|
fontWeight: 600,
|
||||||
background: '#f6f8fa',
|
fontSize: '0.9rem',
|
||||||
color: '#424242',
|
color: '#24292e',
|
||||||
border: '1px solid #d1d5da',
|
backgroundColor: '#eff3f6',
|
||||||
borderTopLeftRadius: 3,
|
border: '1px solid rgba(27,31,35,.2)',
|
||||||
borderTopRightRadius: 3,
|
borderRadius: 3,
|
||||||
margin: '-1px -1px 0',
|
':hover': {
|
||||||
display: 'flex',
|
backgroundColor: '#e6ebf1',
|
||||||
flexDirection: 'row',
|
borderColor: 'rgba(27,31,35,.35)'
|
||||||
alignItems: 'center',
|
},
|
||||||
justifyContent: 'space-between',
|
':active': {
|
||||||
'@media (max-width: 700px)': {
|
backgroundColor: '#e9ecef',
|
||||||
paddingRight: 20,
|
borderColor: 'rgba(27,31,35,.35)',
|
||||||
paddingLeft: 20
|
boxShadow: 'inset 0 0.15em 0.3em rgba(27,31,35,.15)'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>{formatBytes(size)}</span> <span>{language}</span>{' '}
|
View Raw
|
||||||
<a
|
</a>
|
||||||
title={filename}
|
</span>
|
||||||
href={`/${packageName}@${packageVersion}${path}`}
|
</ContentAreaHeaderBar>
|
||||||
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 ? (
|
{highlights ? (
|
||||||
<CodeListing highlights={highlights} />
|
<CodeListing highlights={highlights} />
|
||||||
|
@ -193,7 +170,7 @@ export default function FileViewer({ path, details }) {
|
||||||
) : (
|
) : (
|
||||||
<BinaryViewer />
|
<BinaryViewer />
|
||||||
)}
|
)}
|
||||||
</div>
|
</ContentArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import sortBy from 'sort-by';
|
||||||
|
|
||||||
import { formatBytes } from '../utils/format.js';
|
import { formatBytes } from '../utils/format.js';
|
||||||
|
|
||||||
import { DirectoryIcon, CodeFileIcon } from './Icons.js';
|
import { ContentArea, ContentAreaHeaderBar } from './ContentArea.js';
|
||||||
|
import { FolderIcon, FileIcon, FileCodeIcon } from './Icons.js';
|
||||||
|
|
||||||
const linkStyle = {
|
const linkStyle = {
|
||||||
color: '#0076ff',
|
color: '#0076ff',
|
||||||
|
@ -48,7 +49,26 @@ function getRelName(path, base) {
|
||||||
return path.substr(base.length > 1 ? base.length + 1 : 1);
|
return path.substr(base.length > 1 ? base.length + 1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DirectoryViewer({ path, details: entries }) {
|
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 = [];
|
const rows = [];
|
||||||
|
|
||||||
if (path !== '/') {
|
if (path !== '/') {
|
||||||
|
@ -66,30 +86,14 @@ export default function DirectoryViewer({ path, details: entries }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { subdirs, files } = Object.keys(entries).reduce(
|
subdirs.forEach(({ path: dirname }) => {
|
||||||
(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')).forEach(({ path: dirname }) => {
|
|
||||||
const relName = getRelName(dirname, path);
|
const relName = getRelName(dirname, path);
|
||||||
const href = relName + '/';
|
const href = relName + '/';
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
<tr key={relName}>
|
<tr key={relName}>
|
||||||
<td css={iconCellStyle}>
|
<td css={iconCellStyle}>
|
||||||
<DirectoryIcon />
|
<FolderIcon />
|
||||||
</td>
|
</td>
|
||||||
<td css={tableCellStyle}>
|
<td css={tableCellStyle}>
|
||||||
<a title={relName} href={href} css={linkStyle}>
|
<a title={relName} href={href} css={linkStyle}>
|
||||||
|
@ -102,40 +106,44 @@ export default function DirectoryViewer({ path, details: entries }) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
files
|
files.forEach(({ path: filename, size, contentType }) => {
|
||||||
.sort(sortBy('path'))
|
const relName = getRelName(filename, path);
|
||||||
.forEach(({ path: filename, size, contentType }) => {
|
const href = relName;
|
||||||
const relName = getRelName(filename, path);
|
|
||||||
const href = relName;
|
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
<tr key={relName}>
|
<tr key={relName}>
|
||||||
<td css={iconCellStyle}>
|
<td css={iconCellStyle}>
|
||||||
<CodeFileIcon />
|
{contentType === 'text/plain' || contentType === 'text/markdown' ? (
|
||||||
</td>
|
<FileIcon />
|
||||||
<td css={tableCellStyle}>
|
) : (
|
||||||
<a title={relName} href={href} css={linkStyle}>
|
<FileCodeIcon />
|
||||||
{relName}
|
)}
|
||||||
</a>
|
</td>
|
||||||
</td>
|
<td css={tableCellStyle}>
|
||||||
<td css={tableCellStyle}>{formatBytes(size)}</td>
|
<a title={relName} href={href} css={linkStyle}>
|
||||||
<td css={typeCellStyle}>{contentType}</td>
|
{relName}
|
||||||
</tr>
|
</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 (
|
return (
|
||||||
<div
|
<ContentArea>
|
||||||
css={{
|
<ContentAreaHeaderBar>
|
||||||
border: '1px solid #dfe2e5',
|
<span>{counts.join(', ')}</span>
|
||||||
borderRadius: 3,
|
</ContentAreaHeaderBar>
|
||||||
borderTopWidth: 0,
|
|
||||||
'@media (max-width: 700px)': {
|
|
||||||
borderRightWidth: 0,
|
|
||||||
borderLeftWidth: 0
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<table
|
<table
|
||||||
css={{
|
css={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -146,6 +154,9 @@ export default function DirectoryViewer({ path, details: entries }) {
|
||||||
'& th + th + th + th, & td + td + td + td': {
|
'& th + th + th + th, & td + td + td + td': {
|
||||||
display: 'none'
|
display: 'none'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'& tr:first-child td': {
|
||||||
|
borderTop: 0
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -167,12 +178,12 @@ export default function DirectoryViewer({ path, details: entries }) {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{rows}</tbody>
|
<tbody>{rows}</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</ContentArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
DirectoryViewer.propTypes = {
|
FolderViewer.propTypes = {
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
details: PropTypes.objectOf(
|
details: PropTypes.objectOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
|
@ -1,18 +1,27 @@
|
||||||
/** @jsx jsx */
|
/** @jsx jsx */
|
||||||
import { jsx } from '@emotion/core';
|
import { jsx } from '@emotion/core';
|
||||||
import { GoFileDirectory, GoFile } from 'react-icons/go';
|
import {
|
||||||
|
GoArrowBoth,
|
||||||
|
GoFile,
|
||||||
|
GoFileCode,
|
||||||
|
GoFileDirectory
|
||||||
|
} from 'react-icons/go';
|
||||||
import { FaTwitter, FaGithub } from 'react-icons/fa';
|
import { FaTwitter, FaGithub } from 'react-icons/fa';
|
||||||
|
|
||||||
function createIcon(Type, { css, ...rest }) {
|
function createIcon(Type, { css, ...rest }) {
|
||||||
return <Type css={{ ...css, verticalAlign: 'text-bottom' }} {...rest} />;
|
return <Type css={{ ...css, verticalAlign: 'text-bottom' }} {...rest} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DirectoryIcon(props) {
|
export function FileIcon(props) {
|
||||||
return createIcon(GoFileDirectory, props);
|
return createIcon(GoFile, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CodeFileIcon(props) {
|
export function FileCodeIcon(props) {
|
||||||
return createIcon(GoFile, props);
|
return createIcon(GoFileCode, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FolderIcon(props) {
|
||||||
|
return createIcon(GoFileDirectory, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TwitterIcon(props) {
|
export function TwitterIcon(props) {
|
||||||
|
@ -22,3 +31,7 @@ export function TwitterIcon(props) {
|
||||||
export function GitHubIcon(props) {
|
export function GitHubIcon(props) {
|
||||||
return createIcon(FaGithub, props);
|
return createIcon(FaGithub, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ArrowBothIcon(props) {
|
||||||
|
return createIcon(GoArrowBoth, props);
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import React, { createContext, useContext } from 'react';
|
|
||||||
|
|
||||||
const Context = createContext();
|
|
||||||
|
|
||||||
export function PackageInfoProvider({ children, ...rest }) {
|
|
||||||
return <Context.Provider children={children} value={rest} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePackageInfo() {
|
|
||||||
return useContext(Context);
|
|
||||||
}
|
|
|
@ -41,6 +41,9 @@ const globalStyles = css`
|
||||||
|
|
||||||
code {
|
code {
|
||||||
${fontMono}
|
${fontMono}
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0 3px;
|
||||||
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
dd,
|
dd,
|
||||||
|
@ -58,9 +61,7 @@ function Link(props) {
|
||||||
css={{
|
css={{
|
||||||
color: '#0076ff',
|
color: '#0076ff',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
':hover': {
|
':hover': { textDecoration: 'underline' }
|
||||||
textDecoration: 'underline'
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -127,7 +128,10 @@ export default function App() {
|
||||||
css={{
|
css={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: '4.5em',
|
fontSize: '4.5em',
|
||||||
letterSpacing: '0.05em'
|
letterSpacing: '0.05em',
|
||||||
|
'@media (min-width: 700px)': {
|
||||||
|
margin: '1.5em 0 1em'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
UNPKG
|
UNPKG
|
||||||
|
@ -365,10 +369,11 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The origin infrastructure runs on{' '}
|
The origin servers run on world-class auto-scaling infrastructure
|
||||||
|
provided by{' '}
|
||||||
<Link href="https://cloud.google.com/">Google Cloud</Link> which
|
<Link href="https://cloud.google.com/">Google Cloud</Link> which
|
||||||
automatically scales the number of available servers to meet the
|
dynamically adjusts the number of available servers to meet the
|
||||||
current demand.
|
current demand for maximum efficiency and uptime.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Reference in New Issue