Skip to content

Commit

Permalink
FileView: added file preview for images/videos/pdf/text files
Browse files Browse the repository at this point in the history
  • Loading branch information
warpdesign committed Apr 23, 2024
1 parent 01d1031 commit 87980ad
Show file tree
Hide file tree
Showing 20 changed files with 3,474 additions and 14,994 deletions.
18,213 changes: 3,258 additions & 14,955 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-explorer",
"version": "3.1.0",
"version": "4.0.0",
"description": "Plugin-based file explorer written with React",
"main": "build/main.js",
"build": {
Expand Down Expand Up @@ -28,6 +28,9 @@
"build/**/*"
]
},
"overrides": {
"nan": "github:jkleinsc/nan#remove_accessor_signature"
},
"scripts": {
"test": "jest",
"test:e2e": "npm run server:stop && cd e2e && npm run build && npm run server && npm run cypress:run --config video=false && pm2 stop cy-server",
Expand Down Expand Up @@ -79,8 +82,8 @@
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"electron": "^21.0.1",
"electron-builder": "^23.2.0",
"electron": "^22.3.27",
"electron-builder": "^24.13.3",
"electron-devtools-installer": "^3.1.1",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
Expand Down Expand Up @@ -135,6 +138,7 @@
"react": "^16.9.0",
"react-dnd": "^14.0.5",
"react-dnd-html5-backend": "^14.1.0",
"react-doc-viewer": "git+https://[email protected]/warpdesign/react-doc-viewer.git#release",
"react-dom": "^16.9.0",
"react-i18next": "^12.0.0",
"react-virtual": "^2.10.4"
Expand Down
17 changes: 8 additions & 9 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useCallback, useState, useRef } from 'react'
import { ipcRenderer } from 'electron'
import { ipcRenderer, webFrame } from 'electron'
import { platform } from 'process'
import { FocusStyleManager, Alert, Classes, Intent } from '@blueprintjs/core'
import classNames from 'classnames'
Expand Down Expand Up @@ -31,6 +31,7 @@ import '$src/css/scrollbars.css'
import { reaction } from 'mobx'
import { ReactiveProperties } from '$src/types'
import { triggerUpdateMenus } from '$src/events'
import { PreviewDialog } from './dialogs/PreviewDialog'

const App = observer(() => {
const { appState } = useStores('appState')
Expand All @@ -44,6 +45,7 @@ const App = observer(() => {
isPrefsOpen,
isShortcutsOpen,
isExplorer,
activeView,
} = appState

const cache = appState.getActiveCache()
Expand Down Expand Up @@ -126,6 +128,8 @@ const App = observer(() => {

ipcRenderer.on('exitRequest', onExitRequest)

webFrame.setVisualZoomLevelLimits(1, 4)

return () => {
ipcRenderer.removeAllListeners('exitRequest')
}
Expand Down Expand Up @@ -168,17 +172,11 @@ const App = observer(() => {
return reaction(
(): ReactiveProperties => getReactiveProps(),
(value) => {
console.log('something changed!')
triggerUpdateMenus(t('APP_MENUS', { returnObjects: true }), value)
},
{
equals: (value: ReactiveProperties, previousValue: ReactiveProperties) => {
console.log(JSON.stringify(value) === JSON.stringify(previousValue))
console.log(JSON.stringify(value))
console.log(JSON.stringify(previousValue))

return JSON.stringify(value) === JSON.stringify(previousValue)
},
equals: (value: ReactiveProperties, previousValue: ReactiveProperties) =>
JSON.stringify(value) === JSON.stringify(previousValue),
},
)
}, [])
Expand Down Expand Up @@ -304,6 +302,7 @@ const App = observer(() => {
{splitView && <SideView viewState={views[1]} hide={!isExplorer} />}
<Downloads hide={isExplorer} />
</div>
<PreviewDialog />
</React.Fragment>
</Provider>
)
Expand Down
34 changes: 25 additions & 9 deletions src/components/FileView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ipcRenderer } from 'electron'

import { FileDescriptor, sameID } from '$src/services/Fs'
import { formatBytes } from '$src/utils/formatBytes'
import { isEditable } from '$src/utils/dom'
import { isEditable, shouldCatchEvent } from '$src/utils/dom'
import { isMac } from '$src/utils/platform'
import { FileState } from '$src/state/fileState'
import { FileContextMenu } from '$src/components/menus/FileContextMenu'
Expand Down Expand Up @@ -90,10 +90,9 @@ const FileView = observer(({ hide }: Props) => {
iconSize: 56,
isSplitViewActive: winState.splitView,
}
console.log('render!', { cursorIndex, cursor })

const searchStringRef = React.useRef<string>('')
const timeStampRef = React.useRef<number>(0)
const searchStringRef = useRef<string>('')
const timeStampRef = useRef<number>(0)

// quick select
useKeyDown(
Expand Down Expand Up @@ -132,28 +131,32 @@ const FileView = observer(({ hide }: Props) => {
useKeyDown(
React.useCallback(
(event: KeyboardEvent) => {
if (!viewState.isActive || !appState.isExplorer) {
if (
!viewState.isActive ||
!appState.isExplorer ||
(!appState.isPreviewOpen && !shouldCatchEvent(event))
) {
return
}

switch (event.key) {
case 'ArrowUp':
case 'ArrowDown':
case 'ArrowRight':
case 'ArrowLeft':
case 'ArrowLeft': {
// Prevent arrow keys to trigger generic browser scrolling: we want to handle it
// ourselves so that the cursor is always visible.
event.preventDefault()
const { getNextIndex } = getActions()
console.log('usekeydown (render)', viewmode, viewmodeRef.current.icons)
const nextIndex = getNextIndex(cursorIndex, event.key as ArrowKey)
if (nextIndex > -1 && nextIndex <= rowCount - 1) {
const file = cache.files[nextIndex]
selectFile(file, false, event.shiftKey)
}
break
}

case 'Enter':
case 'Enter': {
const item = nodes[cursorIndex]
if (
item.isSelected &&
Expand All @@ -163,11 +166,24 @@ const FileView = observer(({ hide }: Props) => {
cache.setEditingFile(cursor)
}
break
}

case ' ': {
event.preventDefault()
const item = nodes[cursorIndex]
if (item && !appState.isPreviewOpen) {
appState.togglePreviewDialog(true)
} else {
appState.togglePreviewDialog(false)
}
break
}
}
},
[cursor, cache, rowCount],
),
['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Enter'],
['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Enter', ' '],
{ alwaysCatchEvent: true },
)

useMenuAccelerator([
Expand Down
1 change: 0 additions & 1 deletion src/components/TabList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ const TabList = observer(() => {
}

const onFolderContextMenu = (index: number, e: React.MouseEvent): void => {
console.log('right click')
e.preventDefault()
e.stopPropagation()

Expand Down
2 changes: 0 additions & 2 deletions src/components/Toolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export const Toolbar = observer(({ active }: Props) => {
}

try {
console.log("Let's create a directory :)", dirName, navigate)
const dir = await cache.makedir(path, dirName)

if (!navigate) {
Expand Down Expand Up @@ -150,7 +149,6 @@ export const Toolbar = observer(({ active }: Props) => {
const onFileAction = (action: string): void => {
switch (action) {
case 'makedir':
console.log('Opening new folder dialog')
onMakedir()
break

Expand Down
2 changes: 0 additions & 2 deletions src/components/dialogs/LoginDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ interface LoginState {
busy?: boolean
}

const ENTER_KEY = 13

class LoginDialogClass extends React.Component<LoginProps, LoginState> {
private input: HTMLInputElement | null = null

Expand Down
128 changes: 128 additions & 0 deletions src/components/dialogs/PreviewDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, { KeyboardEvent as KE, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useStores } from '$src/hooks/useStores'
import { Button, Classes, Colors, Dialog, Icon } from '@blueprintjs/core'
import { observer } from 'mobx-react'
import DocViewer, { DocViewerRenderers } from 'react-doc-viewer'
import { TypeIcons } from '$src/constants/icons'
import { formatBytes } from '$src/utils/formatBytes'
import { FileDescriptor } from '$src/services/Fs'
import { FileState } from '$src/state/fileState'
import { useTranslation } from 'react-i18next'

const lightTheme = {
tertiary: Colors.WHITE,
disableThemeScrollbar: true,
}

const darkTheme = {
tertiary: Colors.DARK_GRAY3,
textPrimary: Colors.WHITE,
textSecondary: Colors.LIGHT_GRAY1,
textTertiary: Colors.LIGHT_GRAY5,
disableThemeScrollbar: true,
}

export const PreviewDialog = observer(() => {
const { appState, settingsState } = useStores('appState', 'settingsState')
const { isPreviewOpen } = appState
const view = appState.activeView
const cache = view.getVisibleCache()
const cursorIndex = cache.getFileIndex(cache.cursor)
const docs = useMemo(() => {
return cache.files.map((file) => ({ uri: cache.join(file.dir, file.fullname).replace(/#/g, '%23') }))
}, [cache.cursor])
const activeDocument = docs[cursorIndex]
const { isDir, type, length, mDate } = cache.cursor || ({} as FileDescriptor)
const icon = (isDir && TypeIcons['dir']) || (type && TypeIcons[type]) || TypeIcons['any']
const size = (length && formatBytes(length)) || 0
const theme = settingsState.isDarkModeActive ? darkTheme : lightTheme

const Header = ({ cache }: { cache: FileState }) => {
const file = cache.cursor
const { t } = useTranslation()

return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexShrink: 1,
columnGap: '20px',
}}
>
<p
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
margin: 0,
}}
>
{file.fullname}
</p>
{file.isDir === false && (
<Button onClick={() => cache.openFile(appState, cache.cursor)}>{t('DIALOG.PREVIEW.OPEN')}</Button>
)}
</div>
)
}

const NoPreviewRenderer = ({ document, fileName }: any) => {
const { t } = useTranslation()
const fileText = fileName || document?.fileType || ''
const modifiedString = (mDate && t('DIALOG.PREVIEW.LAST_MODIFIED_ON', { date: mDate.toLocaleString() })) || ''

return (
<div style={{ display: 'flex', padding: '16px 32px', columnGap: '40px' }}>
<div>
<Icon icon={icon} size={100} color={Colors.GRAY3} />
</div>
<div>
{fileText && <h3>{fileText}</h3>}
<p
style={{
color: Colors.GRAY3,
}}
>
{!isDir && <>{size}</>}
<br />
{modifiedString}
</p>
</div>
</div>
)
}

return (
cache.cursor && (
<Dialog
icon={icon}
style={{ width: '66%' }}
title={<Header cache={cache} />}
isOpen={isPreviewOpen}
onClose={() => appState.togglePreviewDialog(false)}
>
<div className={Classes.DIALOG_BODY}>
<DocViewer
style={{
maxHeight: '80vh',
}}
config={{
header: {
disableHeader: true,
},
noRenderer: {
overrideComponent: NoPreviewRenderer,
},
}}
documents={docs}
initialActiveDocument={activeDocument}
activeDocument={activeDocument}
pluginRenderers={DocViewerRenderers}
theme={theme}
/>
</div>
</Dialog>
)
)
})
1 change: 1 addition & 0 deletions src/components/dialogs/ShortcutsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const buildShortcuts = (t: TFunction<'translation', undefined>) => ({
{ combo: 'mod + alt + shift + v', label: t('NAV.SPLITVIEW') },
],
[t('SHORTCUT.GROUP.ACTIVE_VIEW')]: [
{ combo: 'space', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_PREVIEW') },
{ combo: (isMac && 'mod + left') || 'alt + left', label: t('SHORTCUT.ACTIVE_VIEW.BACKWARD_HISTORY') },
{ combo: (isMac && 'mod + right') || 'alt + right', label: t('SHORTCUT.ACTIVE_VIEW.FORWARD_HISTORY') },
{ combo: 'meta + c', label: t('SHORTCUT.ACTIVE_VIEW.COPY') },
Expand Down
4 changes: 0 additions & 4 deletions src/components/shortcuts/KeyboardHotkeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,14 @@ class KeyboardHotkeysClass extends React.Component<WithTranslation> {

onBackwardHistory = (): void => {
const cache = this.getActiveFileCache()
console.log('onBackwardHistory')
if (cache) {
console.log('if cache')
cache.navHistory(-1)
}
}

onForwardHistory = (): void => {
const cache = this.getActiveFileCache()
console.log('onForwardHistory')
if (cache) {
console.log('if cache')
cache.navHistory(1)
}
}
Expand Down
1 change: 0 additions & 1 deletion src/components/shortcuts/MenuAccelerators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ class MenuAcceleratorsClass extends React.Component<Props> {

onReloadFileView = (): void => {
if (this.appState.isExplorer) {
console.log('reloading view' /*, this.state.activeView*/)
this.appState.refreshActiveView(/*this.state.activeView*/)
} else {
console.log('downloads active, no refresh')
Expand Down
2 changes: 1 addition & 1 deletion src/css/main.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
body{
body {
margin:0;
overflow:hidden;
height:100vh;
Expand Down
1 change: 0 additions & 1 deletion src/electron/osSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { app } from 'electron'
type App = { getPath: (name: string) => string } | Electron.App

function getAppInstance(): App {
console.log('getAppInstance', app)
let appInstance: App = app

if (!appInstance) {
Expand Down
Loading

0 comments on commit 87980ad

Please sign in to comment.