diff --git a/components/FileListing.tsx b/components/FileListing.tsx index f49c6b5..71ce95c 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -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 ? ( {emojiIcon ? emojiIcon[0] : '📁'} ) : ( - + )}
@@ -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 => (
= ({ 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 ( <> diff --git a/components/previews/DefaultPreview.tsx b/components/previews/DefaultPreview.tsx index f26c21e..9b581fc 100644 --- a/components/previews/DefaultPreview.tsx +++ b/components/previews/DefaultPreview.tsx @@ -15,7 +15,7 @@ const DefaultPreview: FC<{ file: OdFileObject }> = ({ file }) => {
- +
{file.name}
diff --git a/pages/api/index.ts b/pages/api/index.ts index 28f543f..9a6958e 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -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, }, }) diff --git a/types/index.d.ts b/types/index.d.ts index d03c7c1..0d4867e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,61 +1,60 @@ -export type OdFileObject = { +// API response object for /api?path=, 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=. 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 } } diff --git a/utils/getFileIcon.ts b/utils/getFileIcon.ts index 57ac808..9027486 100644 --- a/utils/getFileIcon.ts +++ b/utils/getFileIcon.ts @@ -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 } diff --git a/utils/getPreviewType.ts b/utils/getPreviewType.ts index 2a0d160..b8d6a31 100644 --- a/utils/getPreviewType.ts +++ b/utils/getPreviewType.ts @@ -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 +}