add font awesome, file icons, and more
This commit is contained in:
parent
bada403a9d
commit
40ac051d33
|
|
@ -0,0 +1,37 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
const Breadcrumb: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => {
|
||||
if (query) {
|
||||
const { path } = query
|
||||
if (Array.isArray(path)) {
|
||||
return (
|
||||
<div className="py-2 text-sm text-gray-600 flex">
|
||||
<div className="p-1 hover:text-black transition-all duration-75">
|
||||
<Link href="/">🚩 Home</Link>
|
||||
</div>
|
||||
{path.map((q: string, i: number) => (
|
||||
<div key={i} className="flex items-center">
|
||||
<div>/</div>
|
||||
<div className="p-1 hover:text-black transition-all duration-75">
|
||||
<Link href={`/${path.slice(0, i + 1).join('/')}`}>{q}</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-2 text-sm text-gray-600 hover:text-black transition-all duration-75">
|
||||
<div className="p-1">
|
||||
<Link href="/">🚩 Home</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Breadcrumb
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
import axios from 'axios'
|
||||
import useSWR from 'swr'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import { FunctionComponent } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import Link from 'next/link'
|
||||
|
||||
import getFileIcon from '../utils/getFileIcon'
|
||||
|
||||
const humanFileSize = (size: number) => {
|
||||
if (size < 1024) return size + ' B'
|
||||
const i = Math.floor(Math.log(size) / Math.log(1024))
|
||||
|
|
@ -12,17 +17,20 @@ const humanFileSize = (size: number) => {
|
|||
return `${formatted} ${'KMGTPEZY'[i - 1]}B`
|
||||
}
|
||||
|
||||
const encodePath = (path: string) => {
|
||||
const encodedPath = path === '/' ? '' : path
|
||||
if (encodedPath === '/' || encodedPath === '') {
|
||||
return ''
|
||||
const queryToPath = (query?: ParsedUrlQuery) => {
|
||||
if (query) {
|
||||
const { path } = query
|
||||
if (!path) return '/'
|
||||
if (typeof path === 'string') return `/${path}`
|
||||
return `/${path.join('/')}`
|
||||
}
|
||||
return encodedPath
|
||||
return '/'
|
||||
}
|
||||
|
||||
const fetcher = (url: string) => axios.get(url).then(res => res.data)
|
||||
|
||||
const FileListing: FunctionComponent<{ path: string }> = ({ path }) => {
|
||||
const FileListing: FunctionComponent<{ query?: ParsedUrlQuery }> = ({ query }) => {
|
||||
const path = queryToPath(query)
|
||||
const { data, error } = useSWR(`/api?path=${path}`, fetcher)
|
||||
if (error) return <div>Failed to load</div>
|
||||
if (!data)
|
||||
|
|
@ -50,19 +58,25 @@ const FileListing: FunctionComponent<{ path: string }> = ({ path }) => {
|
|||
} = data
|
||||
return (
|
||||
<div className="bg-white shadow rounded">
|
||||
<div className="p-3 grid grid-cols-9 items-center space-x-2">
|
||||
<div className="col-span-6 font-bold">Name</div>
|
||||
<div className="p-3 grid grid-cols-10 items-center space-x-2 border-b border-gray-200">
|
||||
<div className="col-span-7 font-bold">Name</div>
|
||||
<div className="font-bold col-span-2">Created</div>
|
||||
<div className="font-bold">Size</div>
|
||||
</div>
|
||||
{children.map((c: any) => (
|
||||
<Link href={`${encodePath(path)}/${c.name}`} key={c.id} passHref>
|
||||
<div className="p-3 grid grid-cols-9 items-center space-x-2 cursor-pointer hover:bg-gray-100">
|
||||
<div className="col-span-6 truncate">{c.name}</div>
|
||||
<div className="font-mono text-sm col-span-2">
|
||||
<Link href={`${path === '/' ? '' : path}/${c.name}`} key={c.id} passHref>
|
||||
<div className="p-3 grid grid-cols-10 items-center space-x-2 cursor-pointer hover:bg-gray-100">
|
||||
<div className="flex space-x-2 items-center col-span-7 truncate">
|
||||
{/* <div>{c.file ? c.file.mimeType : 'folder'}</div> */}
|
||||
<div className="w-5 text-center">
|
||||
<FontAwesomeIcon icon={c.file ? getFileIcon(c.name) : ['far', 'folder']} />
|
||||
</div>
|
||||
<div className="truncate">{c.name}</div>
|
||||
</div>
|
||||
<div className="font-mono text-sm col-span-2 text-gray-700">
|
||||
{new Date(c.createdDateTime).toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' })}
|
||||
</div>
|
||||
<div className="font-mono text-sm">{humanFileSize(c.size)}</div>
|
||||
<div className="font-mono text-sm text-gray-700">{humanFileSize(c.size)}</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
import siteConfig from '../config/site.json'
|
||||
|
||||
const Navbar = () => {
|
||||
return (
|
||||
<div className="text-left bg-white p-2">
|
||||
<div className="max-w-4xl w-full mx-auto">
|
||||
<h1 className="font-bold text-lg">{siteConfig.title}</h1>
|
||||
<div className="text-left bg-white p-3 sticky top-0 bg-opacity-80 backdrop-blur-md shadow-sm">
|
||||
<div className="max-w-4xl w-full mx-auto flex items-center justify-between">
|
||||
<h1 className="font-bold text-xl">{siteConfig.title}</h1>
|
||||
<a href="https://github.com/spencerwooo/onedrive-vercel-index" target="_blank" rel="noopener noreferrer">
|
||||
<FontAwesomeIcon icon={['fab', 'github']} size="lg" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,11 +9,14 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"axios": "^0.21.1",
|
||||
"next": "11.0.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-file-icon": "^1.0.0",
|
||||
"swr": "^0.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
|
||||
import siteConfig from '../config/site.json'
|
||||
import Navbar from '../components/Navbar'
|
||||
import FileListing from '../components/FileListing'
|
||||
import Footer from '../components/Footer'
|
||||
import Breadcrumb from '../components/Breadcrumb'
|
||||
|
||||
export default function Folders() {
|
||||
const { asPath } = useRouter()
|
||||
const { query } = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||
|
|
@ -18,12 +18,9 @@ export default function Folders() {
|
|||
|
||||
<main className="flex flex-col w-full flex-1 bg-gray-50">
|
||||
<Navbar />
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<div className="py-3 text-sm text-gray-600">
|
||||
<Link href="/">🚩 Home </Link>
|
||||
{decodeURIComponent(asPath)}
|
||||
</div>
|
||||
<FileListing path={asPath} />
|
||||
<div className="mx-auto w-full max-w-4xl mb-8">
|
||||
<Breadcrumb query={query} />
|
||||
<FileListing query={query} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,42 @@
|
|||
import '../styles/globals.css'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faFileImage,
|
||||
faFilePdf,
|
||||
faFileWord,
|
||||
faFilePowerpoint,
|
||||
faFileExcel,
|
||||
faFileAudio,
|
||||
faFileVideo,
|
||||
faFileArchive,
|
||||
faFileCode,
|
||||
faFileAlt,
|
||||
faFile,
|
||||
faFolder,
|
||||
faCaretSquareDown,
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
import { faGithub, faMarkdown } from '@fortawesome/free-brands-svg-icons'
|
||||
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
library.add(
|
||||
faFileImage,
|
||||
faFilePdf,
|
||||
faFileWord,
|
||||
faFilePowerpoint,
|
||||
faFileExcel,
|
||||
faFileAudio,
|
||||
faFileVideo,
|
||||
faFileArchive,
|
||||
faFileCode,
|
||||
faFileAlt,
|
||||
faFile,
|
||||
faFolder,
|
||||
faGithub,
|
||||
faMarkdown,
|
||||
faCaretSquareDown
|
||||
)
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ const getAccessToken = async () => {
|
|||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { path = '/' } = req.query
|
||||
if (path === '[...path]') {
|
||||
res.status(400).json({ error: 'Path query invalid.' })
|
||||
}
|
||||
|
||||
if (typeof path === 'string') {
|
||||
const accessToken = await getAccessToken()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
|
||||
import siteConfig from '../config/site.json'
|
||||
import Navbar from '../components/Navbar'
|
||||
import FileListing from '../components/FileListing'
|
||||
import Footer from '../components/Footer'
|
||||
import Breadcrumb from '../components/Breadcrumb'
|
||||
|
||||
export default function Home() {
|
||||
const { asPath } = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||
<Head>
|
||||
|
|
@ -18,11 +15,9 @@ export default function Home() {
|
|||
|
||||
<main className="flex flex-col w-full flex-1 bg-gray-50">
|
||||
<Navbar />
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<div className="py-3 text-sm text-gray-600">
|
||||
<Link href="/">🚩 Home</Link>
|
||||
</div>
|
||||
<FileListing path={asPath} />
|
||||
<div className="mx-auto w-full max-w-4xl mb-8">
|
||||
<Breadcrumb />
|
||||
<FileListing />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
const icons = {
|
||||
image: ['far', 'file-image'],
|
||||
pdf: ['far', 'file-pdf'],
|
||||
word: ['far', 'file-word'],
|
||||
powerpoint: ['far', 'file-powerpoint'],
|
||||
excel: ['far', 'file-excel'],
|
||||
audio: ['far', 'file-audio'],
|
||||
video: ['far', 'file-video'],
|
||||
archive: ['far', 'file-archive'],
|
||||
code: ['far', 'file-code'],
|
||||
text: ['far', 'file-alt'],
|
||||
file: ['far', 'file'],
|
||||
markdown: ['fab', 'markdown'],
|
||||
}
|
||||
|
||||
const extensions = {
|
||||
gif: icons.image,
|
||||
jpeg: icons.image,
|
||||
jpg: icons.image,
|
||||
png: icons.image,
|
||||
heic: icons.image,
|
||||
webp: icons.image,
|
||||
|
||||
pdf: icons.pdf,
|
||||
|
||||
doc: icons.word,
|
||||
docx: icons.word,
|
||||
|
||||
ppt: icons.powerpoint,
|
||||
pptx: icons.powerpoint,
|
||||
|
||||
xls: icons.excel,
|
||||
xlsx: icons.excel,
|
||||
|
||||
aac: icons.audio,
|
||||
mp3: icons.audio,
|
||||
ogg: icons.audio,
|
||||
|
||||
avi: icons.video,
|
||||
flv: icons.video,
|
||||
mkv: icons.video,
|
||||
mp4: icons.video,
|
||||
|
||||
gz: icons.archive,
|
||||
rar: icons.archive,
|
||||
zip: icons.archive,
|
||||
|
||||
css: icons.code,
|
||||
py: icons.code,
|
||||
html: icons.code,
|
||||
js: icons.code,
|
||||
ts: icons.code,
|
||||
c: icons.code,
|
||||
rb: icons.code,
|
||||
cpp: icons.code,
|
||||
|
||||
txt: icons.text,
|
||||
rtf: icons.text,
|
||||
md: icons.markdown,
|
||||
}
|
||||
|
||||
/**
|
||||
* To stop TypeScript complaining about indexing the object with a non-existent key
|
||||
* https://dev.to/mapleleaf/indexing-objects-in-typescript-1cgi
|
||||
*
|
||||
* @param obj Object with keys to index
|
||||
* @param key The index key
|
||||
* @returns Whether or not the key exists inside the object
|
||||
*/
|
||||
const hasKey = <O>(obj: O, key: PropertyKey): key is keyof O => {
|
||||
return key in obj
|
||||
}
|
||||
|
||||
export default function getFileIcon(fileName: string) {
|
||||
const extension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
|
||||
return hasKey(extensions, extension) ? extensions[extension] : icons.file
|
||||
}
|
||||
Loading…
Reference in New Issue