client: add file tree to post view

This commit is contained in:
Max Leiter 2022-03-24 20:24:40 -07:00
parent 448c443e2e
commit dfea957046
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
7 changed files with 311 additions and 103 deletions

View file

@ -26,7 +26,7 @@ const App = ({
accents_6: 'var(--darker-gray)', accents_6: 'var(--darker-gray)',
accents_7: 'var(--darkest-gray)', accents_7: 'var(--darkest-gray)',
accents_8: 'var(--darkest-gray)', accents_8: 'var(--darkest-gray)',
border: 'var(--light-gray)', border: 'var(--lightest-gray)',
}, },
expressiveness: { expressiveness: {
dropdownBoxShadow: '0 0 0 1px var(--light-gray)', dropdownBoxShadow: '0 0 0 1px var(--light-gray)',

View file

@ -0,0 +1,99 @@
.fileTreeWrapper {
display: block;
position: absolute;
left: 0;
height: 100%;
}
.fileTreeWrapper h5 {
text-align: center;
}
.fileTree {
list-style: none;
width: 100%;
}
.fileTree li {
transition: var(--transition);
border-radius: var(--radius);
padding: var(--gap-half) 0;
margin: 0;
}
.fileTree li:hover {
background: var(--lighter-gray);
}
.fileTree li .fileTreeIcon {
display: inline-block;
padding-right: var(--gap-half);
padding-left: var(--gap-half);
}
.fileTree li .fileTreeTitle {
font-size: 1.1rem;
}
.fileTree li::before {
content: "";
padding: 0;
margin: 0;
}
.card {
top: 0;
overflow-y: scroll;
overflow-x: hidden;
position: fixed;
}
.cardContent {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: var(--gap-half);
padding-top: 200px;
}
@media screen and (max-width: 82rem) {
.fileTreeWrapper {
position: relative;
width: 100%;
height: auto;
margin-top: var(--gap);
}
.card {
position: relative;
padding: 0;
}
.cardContent {
margin: var(--gap);
padding: 0;
}
.fileTree {
width: 100%;
}
.fileTree li {
padding: 0.5rem 0;
}
.fileTree li .fileTreeIcon {
margin-right: var(--gap-half);
}
.fileTree li .fileTreeTitle {
font-size: 1.2rem;
}
.fileTree li::before {
content: "";
padding: 0;
margin: 0;
}
}

View file

@ -0,0 +1,61 @@
import { File } from "@lib/types"
import { Card, Link, Text } from '@geist-ui/core'
import FileIcon from '@geist-ui/icons/fileText'
import CodeIcon from '@geist-ui/icons/fileLambda'
import styles from './file-tree.module.css'
import ShiftBy from "@components/shift-by"
import { useEffect, useState } from "react"
import { codeFileExtensions } from "@lib/constants"
type Item = File & {
icon: JSX.Element
}
const FileTree = ({
files
}: {
files: File[]
}) => {
const [items, setItems] = useState<Item[]>([])
useEffect(() => {
const newItems = files.map(file => {
const extension = file.title.split('.').pop()
if (codeFileExtensions.includes(extension || '')) {
return {
...file,
icon: <CodeIcon />
}
} else {
return {
...file,
icon: <FileIcon />
}
}
})
setItems(newItems)
}, [files])
// a list of files with an icon and a title
return (
<div className={styles.fileTreeWrapper}>
<Card height={'100%'} className={styles.card}>
<div className={styles.cardContent}>
<Text h4>Files</Text>
<ul className={styles.fileTree}>
{items.map(({ id, title, icon }) => (
<li key={id}>
<Link color={false} href={`#${title}`}>
<ShiftBy y={5}><span className={styles.fileTreeIcon}>
{icon}</span></ShiftBy>
<span className={styles.fileTreeTitle}>{title}</span>
</Link>
</li>
))}
</ul>
</div>
</Card>
</div >
)
}
export default FileTree

View file

@ -4,103 +4,8 @@ import { useDropzone } from 'react-dropzone'
import styles from './drag-and-drop.module.css' import styles from './drag-and-drop.module.css'
import type { Document } from '@lib/types' import type { Document } from '@lib/types'
import generateUUID from '@lib/generate-uuid' import generateUUID from '@lib/generate-uuid'
const allowedFileTypes = [ import { allowedFileTypes, allowedFileNames, allowedFileExtensions } from '@lib/constants'
'application/json',
'application/x-javascript',
'application/xhtml+xml',
'application/xml',
'text/xml',
'text/plain',
'text/html',
'text/csv',
'text/tab-separated-values',
'text/x-c',
'text/x-c++',
'text/x-csharp',
'text/x-java',
'text/x-javascript',
'text/x-php',
'text/x-python',
'text/x-ruby',
'text/x-scala',
'text/x-swift',
'text/x-typescript',
'text/x-vb',
'text/x-vbscript',
'text/x-yaml',
'text/x-c++',
'text/x-c#',
'text/mathml',
'text/x-markdown',
'text/markdown',
]
const allowedFileNames = [
'Makefile',
'README',
'Dockerfile',
'Jenkinsfile',
'LICENSE',
'.env',
'.gitignore',
'.gitattributes',
'.env.example',
'.env.development',
'.env.production',
'.env.test',
'.env.staging',
'.env.development.local',
'yarn.lock',
'.bash',
'.bashrc',
'.bash_profile',
'.bash_logout',
'.profile',
'.fish_prompt',
'.zshrc',
'.zsh',
'.zprofile',
]
const allowedFileExtensions = [
'json',
'js',
'jsx',
'ts',
'tsx',
'c',
'cpp',
'c++',
'c#',
'java',
'php',
'py',
'rb',
'scala',
'swift',
'vb',
'vbscript',
'yaml',
'less',
'stylus',
'styl',
'sass',
'scss',
'lock',
'md',
'markdown',
'txt',
'html',
'htm',
'css',
'csv',
'log',
'sql',
'xml',
'webmanifest',
'vue',
'vuex',
]
function FileDropzone({ setDocs }: { setDocs: ((docs: Document[]) => void) }) { function FileDropzone({ setDocs }: { setDocs: ((docs: Document[]) => void) }) {
const { palette } = useTheme() const { palette } = useTheme()

View file

@ -10,6 +10,7 @@ import { Page, Button, Text, Badge, Tooltip, Spacer } from "@geist-ui/core"
import ShiftBy from "@components/shift-by" import ShiftBy from "@components/shift-by"
import { useMemo, useState } from "react" import { useMemo, useState } from "react"
import timeAgo from "@lib/time-ago" import timeAgo from "@lib/time-ago"
import FileTree from "@components/file-tree"
type Props = { type Props = {
post: Post post: Post
@ -61,6 +62,7 @@ const PostPage = ({ post }: Props) => {
Download as ZIP archive Download as ZIP archive
</Button> </Button>
</div> </div>
{post.files.length > 1 && <FileTree files={post.files} />}
{post.files.map(({ id, content, title }: File) => ( {post.files.map(({ id, content, title }: File) => (
<DocumentComponent <DocumentComponent
key={id} key={id}

141
client/lib/constants.ts Normal file
View file

@ -0,0 +1,141 @@
export const allowedFileTypes = [
'application/json',
'application/x-javascript',
'application/xhtml+xml',
'application/xml',
'text/xml',
'text/plain',
'text/html',
'text/csv',
'text/tab-separated-values',
'text/x-c',
'text/x-c++',
'text/x-csharp',
'text/x-java',
'text/x-javascript',
'text/x-php',
'text/x-python',
'text/x-ruby',
'text/x-scala',
'text/x-swift',
'text/x-typescript',
'text/x-vb',
'text/x-vbscript',
'text/x-yaml',
'text/x-c++',
'text/x-c#',
'text/mathml',
'text/x-markdown',
'text/markdown',
]
export const allowedFileNames = [
'Makefile',
'README',
'Dockerfile',
'Jenkinsfile',
'LICENSE',
'.env',
'.gitignore',
'.gitattributes',
'.env.example',
'.env.development',
'.env.production',
'.env.test',
'.env.staging',
'.env.development.local',
'yarn.lock',
'.bash',
'.bashrc',
'.bash_profile',
'.bash_logout',
'.profile',
'.fish_prompt',
'.zshrc',
'.zsh',
'.zprofile',
'go',
]
export const allowedFileExtensions = [
'json',
'js',
'jsx',
'ts',
'tsx',
'c',
'cpp',
'c++',
'c#',
'java',
'php',
'py',
'rb',
'scala',
'swift',
'vb',
'vbscript',
'yaml',
'less',
'stylus',
'styl',
'sass',
'scss',
'lock',
'md',
'markdown',
'txt',
'html',
'htm',
'css',
'csv',
'log',
'sql',
'xml',
'webmanifest',
'vue',
'vuex',
'rs',
'zig',
]
export const codeFileExtensions = [
'json',
'js',
'jsx',
'ts',
'tsx',
'c',
'cpp',
'c++',
'c#',
'java',
'php',
'py',
'rb',
'scala',
'swift',
'vb',
'vbscript',
'yaml',
'less',
'stylus',
'styl',
'sass',
'scss',
'html',
'htm',
'css',
'asm',
's',
'sh',
'bat',
'cmd',
'sql',
'xml',
'rust',
'h',
'zig',
'rs',
'go'
]