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
commit 1df85f263e
Signed by untrusted user: GitHub
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 73 deletions

View File

@ -11,8 +11,8 @@ import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import { humanFileSize, formatModifiedDateTime } from '../utils/fileDetails'
import { getExtension, getFileIcon, hasKey } from '../utils/getFileIcon'
import { extensions, preview } from '../utils/getPreviewType'
import { getExtension, getFileIcon } from '../utils/getFileIcon'
import { getPreviewType, preview } from '../utils/getPreviewType'
import { useProtectedSWRInfinite } from '../utils/fetchWithSWR'
import { getBaseUrl } from '../utils/getBaseUrl'
import {
@ -37,6 +37,8 @@ import DefaultPreview from './previews/DefaultPreview'
import { DownloadBtnContainer, PreviewContainer } from './previews/Containers'
import DownloadButtonGroup from './DownloadBtnGtoup'
import { OdFileObject, OdFolderObject } from '../types'
// Disabling SSR for some previews (image gallery view, and PDF view)
const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false })
const EPUBPreview = dynamic(() => import('./previews/EPUBPreview'), { ssr: false })
@ -57,9 +59,7 @@ const queryToPath = (query?: ParsedUrlQuery) => {
return '/'
}
const FileListItem: FC<{
fileContent: { id: string; name: string; size: number; file: Object; lastModifiedDateTime: string }
}> = ({ fileContent: c }) => {
const FileListItem: FC<{ fileContent: OdFolderObject['value'][number] }> = ({ fileContent: c }) => {
const emojiIcon = emojiRegex().exec(c.name)
const renderEmoji = emojiIcon && !emojiIcon.index
@ -71,7 +71,7 @@ const FileListItem: FC<{
{renderEmoji ? (
<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 className="truncate">
@ -185,10 +185,8 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
const fileIsImage = (fileName: string) => {
const fileExtension = getExtension(fileName)
if (hasKey(extensions, fileExtension)) {
if (extensions[fileExtension] === preview.image) {
return true
}
if (getPreviewType(fileExtension) === preview.image) {
return true
}
return false
}
@ -209,12 +207,12 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
// README rendering preparations
let renderReadme = false
let readmeFile = null
let readmeFile = {}
// 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)) {
imagesInFolder.push({
src: c['@microsoft.graph.downloadUrl'],
@ -232,11 +230,11 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
})
// 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
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)]
return hasT && hasF ? 1 : !hasF ? 2 : 0
}
@ -258,7 +256,7 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
setSelected({})
setTotalSelected(0)
} else {
setSelected(Object.fromEntries(getFiles().map((c: any) => [c.id, true])))
setSelected(Object.fromEntries(getFiles().map(c => [c.id, true])))
setTotalSelected(2)
}
}
@ -268,8 +266,8 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
const folderName = path.substring(path.lastIndexOf('/') + 1)
const folder = folderName ? decodeURIComponent(folderName) : undefined
const files = getFiles()
.filter((c: any) => selected[c.id])
.map((c: any) => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] }))
.filter(c => selected[c.id])
.map(c => ({ name: c.name, url: c['@microsoft.graph.downloadUrl'] }))
if (files.length == 1) {
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="col-span-10"
@ -541,13 +539,14 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
}
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 fileName = file.name
const fileExtension = fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2).toLowerCase()
if (hasKey(extensions, fileExtension)) {
switch (extensions[fileExtension]) {
const previewType = getPreviewType(fileExtension, { video: Boolean(file.video) })
if (previewType) {
switch (previewType) {
case preview.image:
return (
<>

View File

@ -15,7 +15,7 @@ const DefaultPreview: FC<{ file: OdFileObject }> = ({ file }) => {
<PreviewContainer>
<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">
<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>

View File

@ -194,7 +194,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { data: identityData } = await axios.get(requestUrl, {
headers: { Authorization: `Bearer ${accessToken}` },
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}` },
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,
$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,
},
})

83
types/index.d.ts vendored
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
'@odata.context': string
name: string
size: number
id: string
lastModifiedDateTime: string
file: {
mimeType: string
hashes: {
quickXorHash: string
sha1Hash: string
sha256Hash: string
}
}
file: { mimeType: string; hashes: { quickXorHash: string; sha1Hash?: string; sha256Hash?: string } }
video?: OdVideoFile
}
export type OdFolderObject = {
'@odata.count': number
value: Array<{
id: string
name: string
lastModifiedDateTime: string
size: number
folder: {
childCount: number
view: {
sortBy: 'name'
sortOrder: 'ascending'
viewType: 'thumbnails'
}
}
}>
// A representation of a OneDrive video file. All fields are declared here, but we mainly use 'width' and 'height'.
export declare type OdVideoFile = {
width: number
height: number
duration: number
bitrate: number
frameRate: number
audioBitsPerSample: number
audioChannels: number
audioFormat: string
audioSamplesPerSecond: number
}
// Search result type which is returned by /api/search?q={query}
export type OdSearchResult = Array<{
// API response object for /api/search?q=<query>. Likewise, this array of items may also contain either files or folders.
export declare type OdSearchResult = Array<{
id: string
name: string
file?: OdFileObject
folder?: OdFolderObject
path: string
parentReference: {
id: string
name: string
path: string
}
parentReference: { id: string; name: string; path: string }
}>
// driveItem type which is returned by /api/item?id={id}
// API response object for /api/item?id={id}. This is primarily used for determining the path of the driveItem by ID.
export type OdDriveItem = {
'@odata.context': string
'@odata.etag': string
id: string
name: string
parentReference: {
driveId: string
driveType: string
id: string
path: string
}
parentReference: { driveId: string; driveType: string; id: string; path: string }
}

View File

@ -94,7 +94,17 @@ export function getExtension(fileName: string): string {
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)
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
}

View File

@ -1,4 +1,4 @@
const preview = {
export const preview = {
markdown: 'markdown',
image: 'image',
text: 'text',
@ -11,7 +11,7 @@ const preview = {
url: 'url',
}
const extensions = {
export const extensions = {
gif: preview.image,
jpeg: preview.image,
jpg: preview.image,
@ -34,13 +34,17 @@ const extensions = {
c: preview.code,
cpp: preview.code,
js: preview.code,
jsx: preview.code,
java: preview.code,
sh: preview.code,
cs: preview.code,
py: preview.code,
css: preview.code,
html: preview.code,
// typescript or video file, determined below
ts: preview.code,
tsx: preview.code,
rs: preview.code,
vue: preview.code,
json: preview.code,
yaml: preview.code,
@ -70,4 +74,19 @@ const extensions = {
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
}