Merge pull request #304 from myl7/ts

This commit is contained in:
Spencer Woo
2022-01-26 15:46:34 +08:00
committed by GitHub
6 changed files with 100 additions and 73 deletions
+21 -22
View File
@@ -11,8 +11,8 @@ import { useRouter } from 'next/router'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { humanFileSize, formatModifiedDateTime } from '../utils/fileDetails' import { humanFileSize, formatModifiedDateTime } from '../utils/fileDetails'
import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon' import { getExtension, getFileIcon } from '../utils/getFileIcon'
import { extensions, preview } from '../utils/getPreviewType' import { getPreviewType, preview } from '../utils/getPreviewType'
import { useProtectedSWRInfinite } from '../utils/fetchWithSWR' import { useProtectedSWRInfinite } from '../utils/fetchWithSWR'
import { getBaseUrl } from '../utils/getBaseUrl' import { getBaseUrl } from '../utils/getBaseUrl'
import { import {
@@ -37,6 +37,8 @@ import DefaultPreview from './previews/DefaultPreview'
import { DownloadBtnContainer, PreviewContainer } from './previews/Containers' import { DownloadBtnContainer, PreviewContainer } from './previews/Containers'
import DownloadButtonGroup from './DownloadBtnGtoup' import DownloadButtonGroup from './DownloadBtnGtoup'
import { OdFileObject, OdFolderObject } from '../types'
// Disabling SSR for some previews (image gallery view, and PDF view) // Disabling SSR for some previews (image gallery view, and PDF view)
const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false }) const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false })
const EPUBPreview = dynamic(() => import('./previews/EPUBPreview'), { ssr: false }) const EPUBPreview = dynamic(() => import('./previews/EPUBPreview'), { ssr: false })
@@ -57,9 +59,7 @@ const queryToPath = (query?: ParsedUrlQuery) => {
return '/' return '/'
} }
const FileListItem: FC<{ const FileListItem: FC<{ fileContent: OdFolderObject['value'][number] }> = ({ fileContent: c }) => {
fileContent: { id: string; name: string; size: number; file: Object; lastModifiedDateTime: string }
}> = ({ fileContent: c }) => {
const emojiIcon = emojiRegex().exec(c.name) const emojiIcon = emojiRegex().exec(c.name)
const renderEmoji = emojiIcon && !emojiIcon.index const renderEmoji = emojiIcon && !emojiIcon.index
@@ -71,7 +71,7 @@ const FileListItem: FC<{
{renderEmoji ? ( {renderEmoji ? (
<span>{emojiIcon ? emojiIcon[0] : '📁'}</span> <span>{emojiIcon ? emojiIcon[0] : '📁'}</span>
) : ( ) : (
<FontAwesomeIcon icon={c.file ? getFileIcon(c.name) : ['far', 'folder']} /> <FontAwesomeIcon icon={c.file ? getFileIcon(c.name, { video: Boolean(c.video) }) : ['far', 'folder']} />
)} )}
</div> </div>
<div className="truncate"> <div className="truncate">
@@ -185,10 +185,8 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
const fileIsImage = (fileName: string) => { const fileIsImage = (fileName: string) => {
const fileExtension = getExtension(fileName) const fileExtension = getExtension(fileName)
if (hasKey(extensions, fileExtension)) { if (getPreviewType(fileExtension) === preview.image) {
if (extensions[fileExtension] === preview.image) { return true
return true
}
} }
return false return false
} }
@@ -209,12 +207,12 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
// README rendering preparations // README rendering preparations
let renderReadme = false let renderReadme = false
let readmeFile = null let readmeFile = {}
// Expand list of API returns into flattened file data // Expand list of API returns into flattened file data
const children = [].concat(...responses.map(r => r.folder.value)) const children = [].concat(...responses.map(r => r.folder.value)) as OdFolderObject['value']
children.forEach((c: any) => { children.forEach(c => {
if (fileIsImage(c.name)) { if (fileIsImage(c.name)) {
imagesInFolder.push({ imagesInFolder.push({
src: c['@microsoft.graph.downloadUrl'], src: c['@microsoft.graph.downloadUrl'],
@@ -232,11 +230,11 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
}) })
// Filtered file list helper // Filtered file list helper
const getFiles = () => children.filter((c: any) => !c.folder && c.name !== '.password') const getFiles = () => children.filter(c => !c.folder && c.name !== '.password')
// File selection // File selection
const genTotalSelected = (selected: { [key: string]: boolean }) => { const genTotalSelected = (selected: { [key: string]: boolean }) => {
const selectInfo = getFiles().map((c: any) => Boolean(selected[c.id])) const selectInfo = getFiles().map(c => Boolean(selected[c.id]))
const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)]
return hasT && hasF ? 1 : !hasF ? 2 : 0 return hasT && hasF ? 1 : !hasF ? 2 : 0
} }
@@ -258,7 +256,7 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
setSelected({}) setSelected({})
setTotalSelected(0) setTotalSelected(0)
} else { } else {
setSelected(Object.fromEntries(getFiles().map((c: any) => [c.id, true]))) setSelected(Object.fromEntries(getFiles().map(c => [c.id, true])))
setTotalSelected(2) setTotalSelected(2)
} }
} }
@@ -268,8 +266,8 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
const folderName = path.substring(path.lastIndexOf('/') + 1) const folderName = path.substring(path.lastIndexOf('/') + 1)
const folder = folderName ? decodeURIComponent(folderName) : undefined const folder = folderName ? decodeURIComponent(folderName) : undefined
const files = getFiles() const files = getFiles()
.filter((c: any) => selected[c.id]) .filter(c => selected[c.id])
.map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] })) .map(c => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] }))
if (files.length == 1) { if (files.length == 1) {
const el = document.createElement('a') const el = document.createElement('a')
@@ -404,7 +402,7 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
/> />
)} )}
{children.map((c: any) => ( {children.map(c => (
<div className="hover:bg-gray-100 dark:hover:bg-gray-850 grid grid-cols-12" key={c.id}> <div className="hover:bg-gray-100 dark:hover:bg-gray-850 grid grid-cols-12" key={c.id}>
<div <div
className="col-span-10" className="col-span-10"
@@ -541,13 +539,14 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
} }
if ('file' in responses[0] && responses.length === 1) { if ('file' in responses[0] && responses.length === 1) {
const { file } = responses[0] const file = responses[0].file as OdFileObject
const downloadUrl = file['@microsoft.graph.downloadUrl'] const downloadUrl = file['@microsoft.graph.downloadUrl']
const fileName = file.name const fileName = file.name
const fileExtension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase() const fileExtension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
if (hasKey(extensions, fileExtension)) { const previewType = getPreviewType(fileExtension, { video: Boolean(file.video) })
switch (extensions[fileExtension]) { if (previewType) {
switch (previewType) {
case preview.image: case preview.image:
return ( return (
<> <>
+1 -1
View File
@@ -15,7 +15,7 @@ const DefaultPreview: FC<{ file: OdFileObject }> = ({ file }) => {
<PreviewContainer> <PreviewContainer>
<div className="md:flex px-5 py-4 md:space-x-8 items-center"> <div className="md:flex px-5 py-4 md:space-x-8 items-center">
<div className="text-center border rounded-lg px-10 py-20 border-gray-900/10 dark:border-gray-500/30"> <div className="text-center border rounded-lg px-10 py-20 border-gray-900/10 dark:border-gray-500/30">
<FontAwesomeIcon icon={getFileIcon(file.name)} /> <FontAwesomeIcon icon={getFileIcon(file.name, { video: Boolean(file.video) })} />
<div className="font-medium text-sm mt-6">{file.name}</div> <div className="font-medium text-sm mt-6">{file.name}</div>
</div> </div>
+3 -3
View File
@@ -194,7 +194,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { data: identityData } = await axios.get(requestUrl, { const { data: identityData } = await axios.get(requestUrl, {
headers: { Authorization: `Bearer ${accessToken}` }, headers: { Authorization: `Bearer ${accessToken}` },
params: { params: {
select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file', select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file,video',
}, },
}) })
@@ -203,12 +203,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
headers: { Authorization: `Bearer ${accessToken}` }, headers: { Authorization: `Bearer ${accessToken}` },
params: next params: next
? { ? {
select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file', select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file,video',
top: siteConfig.maxItems, top: siteConfig.maxItems,
$skipToken: next, $skipToken: next,
} }
: { : {
select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file', select: '@microsoft.graph.downloadUrl,name,size,id,lastModifiedDateTime,folder,file,video',
top: siteConfig.maxItems, top: siteConfig.maxItems,
}, },
}) })
+41 -42
View File
@@ -1,61 +1,60 @@
export type OdFileObject = { // API response object for /api?path=<path_to_file_or_folder>, this may return either a file or a folder.
// Pagination is also declared here with the 'next' parameter.
export declare type OdAPIResponse = { file?: OdFileObject; folder?: OdFolderObject; next?: string }
// A folder object returned from the OneDrive API. This contains the parameter 'value', which is an array of items
// inside the folder. The items may also be either files or folders.
export declare type OdFolderObject = {
'@odata.count': number
'@odata.context': string
'@odata.nextLink'?: string
value: Array<{
'@microsoft.graph.downloadUrl': string
id: string
name: string
size: number
lastModifiedDateTime: string
file?: { mimeType: string; hashes: { quickXorHash: string; sha1Hash?: string; sha256Hash?: string } }
folder?: { childCount: number; view: { sortBy: string; sortOrder: 'ascending'; viewType: 'thumbnails' } }
video?: OdVideoFile
}>
}
// A file object returned from the OneDrive API. This object may contain 'video' if the file is a video.
export declare type OdFileObject = {
'@microsoft.graph.downloadUrl': string '@microsoft.graph.downloadUrl': string
'@odata.context': string
name: string name: string
size: number size: number
id: string id: string
lastModifiedDateTime: string lastModifiedDateTime: string
file: { file: { mimeType: string; hashes: { quickXorHash: string; sha1Hash?: string; sha256Hash?: string } }
mimeType: string video?: OdVideoFile
hashes: {
quickXorHash: string
sha1Hash: string
sha256Hash: string
}
}
} }
// A representation of a OneDrive video file. All fields are declared here, but we mainly use 'width' and 'height'.
export type OdFolderObject = { export declare type OdVideoFile = {
'@odata.count': number width: number
value: Array<{ height: number
id: string duration: number
name: string bitrate: number
lastModifiedDateTime: string frameRate: number
size: number audioBitsPerSample: number
folder: { audioChannels: number
childCount: number audioFormat: string
view: { audioSamplesPerSecond: number
sortBy: 'name'
sortOrder: 'ascending'
viewType: 'thumbnails'
}
}
}>
} }
// API response object for /api/search?q=<query>. Likewise, this array of items may also contain either files or folders.
// Search result type which is returned by /api/search?q={query} export declare type OdSearchResult = Array<{
export type OdSearchResult = Array<{
id: string id: string
name: string name: string
file?: OdFileObject file?: OdFileObject
folder?: OdFolderObject folder?: OdFolderObject
path: string path: string
parentReference: { parentReference: { id: string; name: string; path: string }
id: string
name: string
path: string
}
}> }>
// API response object for /api/item?id={id}. This is primarily used for determining the path of the driveItem by ID.
// driveItem type which is returned by /api/item?id={id}
export type OdDriveItem = { export type OdDriveItem = {
'@odata.context': string '@odata.context': string
'@odata.etag': string '@odata.etag': string
id: string id: string
name: string name: string
parentReference: { parentReference: { driveId: string; driveType: string; id: string; path: string }
driveId: string
driveType: string
id: string
path: string
}
} }
+12 -2
View File
@@ -94,7 +94,17 @@ export function getExtension(fileName: string): string {
return fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase() return fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
} }
export function getFileIcon(fileName: string): [IconPrefix, IconName] { export function getFileIcon(fileName: string, flags?: { video?: boolean }): [IconPrefix, IconName] {
const extension = getExtension(fileName) const extension = getExtension(fileName)
return hasKey(extensions, extension) ? extensions[extension] : icons.file let icon = hasKey(extensions, extension) ? extensions[extension] : icons.file
// Files with '.ts' extensions may be TypeScript files or TS Video files, we check for the flag 'video'
// to determine which icon to render for '.ts' files.
if (extension === 'ts') {
if (flags?.video) {
icon = icons.video
}
}
return icon
} }
+22 -3
View File
@@ -1,4 +1,4 @@
const preview = { export const preview = {
markdown: 'markdown', markdown: 'markdown',
image: 'image', image: 'image',
text: 'text', text: 'text',
@@ -11,7 +11,7 @@ const preview = {
url: 'url', url: 'url',
} }
const extensions = { export const extensions = {
gif: preview.image, gif: preview.image,
jpeg: preview.image, jpeg: preview.image,
jpg: preview.image, jpg: preview.image,
@@ -34,13 +34,17 @@ const extensions = {
c: preview.code, c: preview.code,
cpp: preview.code, cpp: preview.code,
js: preview.code, js: preview.code,
jsx: preview.code,
java: preview.code, java: preview.code,
sh: preview.code, sh: preview.code,
cs: preview.code, cs: preview.code,
py: preview.code, py: preview.code,
css: preview.code, css: preview.code,
html: preview.code, html: preview.code,
// typescript or video file, determined below
ts: preview.code, ts: preview.code,
tsx: preview.code,
rs: preview.code,
vue: preview.code, vue: preview.code,
json: preview.code, json: preview.code,
yaml: preview.code, yaml: preview.code,
@@ -70,4 +74,19 @@ const extensions = {
url: preview.url, url: preview.url,
} }
export { extensions, preview } export function getPreviewType(extension: string, flags?: { video?: boolean }): string | undefined {
let previewType = extensions[extension]
if (!previewType) {
return previewType
}
// Files with '.ts' extensions may be TypeScript files or TS Video files, we check for the flag 'video'
// to determine what preview renderer to use for '.ts' files.
if (extension === 'ts') {
if (flags?.video) {
previewType = preview.video
}
}
return previewType
}