Skip to content

Commit

Permalink
SideView: rewrote using hooks (#341)
Browse files Browse the repository at this point in the history
* SideView: rewrote using hooks

Also:
- fixed enter key not working on makedir dialog
- added missing tests for SideView
- patch HTMLElement.offsetWidth/height so that ReactVirtualized renders tests
  • Loading branch information
warpdesign authored Dec 13, 2022
1 parent 8d1f465 commit e68206c
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 158 deletions.
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
__dirname,
],
moduleNameMapper: {
"\\$src(.*)": "<rootDir>/src/$1"
"\\.(s)?css(.*)$": "identity-obj-proxy",
"\\$src(.*)": "<rootDir>/src/$1"
}
};
34 changes: 34 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"hoist-non-react-statics": "^3.3.0",
"html-webpack-plugin": "^5.5.0",
"husky": "^3.0.9",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.3.1",
"jest-cli": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
Expand Down
12 changes: 6 additions & 6 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import { KeyboardHotkeys } from '$src/components/shortcuts/KeyboardHotkeys'
import { AppState } from '$src/state/appState'
import Keys from '$src/constants/keys'

require('@blueprintjs/core/lib/css/blueprint.css')
require('@blueprintjs/icons/lib/css/blueprint-icons.css')
require('@blueprintjs/popover2/lib/css/blueprint-popover2.css')
require('$src/css/main.css')
require('$src/css/windows.css')
require('$src/css/scrollbars.css')
import '@blueprintjs/core/lib/css/blueprint.css'
import '@blueprintjs/icons/lib/css/blueprint-icons.css'
import '@blueprintjs/popover2/lib/css/blueprint-popover2.css'
import '$src/css/main.css'
import '$src/css/windows.css'
import '$src/css/scrollbars.css'

interface InjectedProps extends WithTranslation {
appState: AppState
Expand Down
2 changes: 1 addition & 1 deletion src/components/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { AppState } from '$src/state/appState'
import { AppAlert } from '$src/components/AppAlert'
import CONFIG from '$src/config/appConfig'

require('$src/css/favoritesPanel.css')
import '$src/css/favoritesPanel.css'

interface LeftPanelState {
nodes: TreeNodeInfo<string>[]
Expand Down
2 changes: 1 addition & 1 deletion src/components/Log.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { debounce } from '$src/utils/debounce'
import { shouldCatchEvent } from '$src/utils/dom'
import Keys from '$src/constants/keys'

require('$src/css/log.css')
import '$src/css/log.css'

export interface JSObject extends Object {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
9 changes: 7 additions & 2 deletions src/components/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import type { ReactElement } from 'react'
interface Props {
active: boolean
children: ReactElement
id?: string
}

export const Overlay = ({ active, children }: Props) => {
export const Overlay = ({ active, children, id = 'overlay' }: Props) => {
const activeClass = (active && 'active') || ''

return <div className={`app-loader ${activeClass}`}>{children}</div>
return (
<div className={`app-loader ${activeClass}`} id={id}>
{children}
</div>
)
}
191 changes: 65 additions & 126 deletions src/components/SideView.tsx
Original file line number Diff line number Diff line change
@@ -1,146 +1,85 @@
import * as React from 'react'
import { Icon, Spinner } from '@blueprintjs/core'
import { inject, Provider, observer } from 'mobx-react'
import { withTranslation, WithTranslation } from 'react-i18next'
import {
DropTargetSpec,
DropTargetConnector,
DropTargetMonitor,
DropTargetCollector,
ConnectDropTarget,
DropTarget,
} from 'react-dnd'
import { Provider, observer } from 'mobx-react'
import { useDrop } from 'react-dnd'
import classNames from 'classnames'

import { Statusbar } from '$src/components/Statusbar'
import { Toolbar } from '$src/components/Toolbar'
import { AppState } from '$src/state/appState'
import { LoginDialog } from '$src/components/dialogs/LoginDialog'
import { Overlay } from '$src/components/Overlay'
import { FileTable } from '$src/components/filetable'
import { TabList } from '$src/components/TabList'
import { ViewState } from '$src/state/viewState'
import { CollectedProps, DraggedObject } from '$src/components/filetable/RowRenderer'
import { DraggedObject } from '$src/components/filetable/RowRenderer'
import { useStores } from '$src/hooks/useStores'

interface SideViewProps extends WithTranslation {
interface SideViewProps {
hide: boolean
viewState: ViewState
onPaste: () => void
connectDropTarget?: ConnectDropTarget
isOver?: boolean
canDrop?: boolean
}

interface InjectedProps extends SideViewProps {
appState?: AppState
}

const fileTarget: DropTargetSpec<InjectedProps> = {
canDrop(props: InjectedProps, monitor): boolean {
// prevent drag and drop in same sideview for now
const sourceViewId = monitor.getItem().fileState.viewId
const viewState = props.viewState
export const SideView = observer(({ hide, viewState, onPaste }: SideViewProps) => {
const { appState } = useStores('appState')
const [{ isOver, canDrop }, drop] = useDrop(
() => ({
accept: 'file',
canDrop: ({ fileState }: DraggedObject) => {
const sourceViewId = fileState.viewId
const fileCache = viewState.getVisibleCache()
return viewState.viewId !== sourceViewId && fileCache.status !== 'busy' && !fileCache.error
},
drop: (item) => appState.drop(item, viewState.getVisibleCache()),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[hide],
)

const onValidation = (/*dir: string*/): boolean => true

const onClose = (): void => {
const fileCache = viewState.getVisibleCache()
return props.viewState.viewId !== sourceViewId && fileCache.status !== 'busy' && !fileCache.error
},
drop(props, monitor, component): void {
const item = monitor.getItem()
const sideView = component
sideView.onDrop(item)
},
}

const collect: DropTargetCollector<CollectedProps, InjectedProps> = (
connect: DropTargetConnector,
monitor: DropTargetMonitor,
) => {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
// doesn't work: it keeps the previous fs
fileCache.revertPath()
}
}

export const SideViewClass = inject('appState')(
observer(
class SideViewClass extends React.Component<InjectedProps> {
constructor(props: InjectedProps) {
super(props)
}

get injected(): InjectedProps {
return this.props as InjectedProps
}

onValidation = (/*dir: string*/): boolean => {
return true
}

onClose = (): void => {
// login cancelled
const { viewState } = this.props
const fileCache = viewState.getVisibleCache()
// doesn't work: it keeps the previous fs
fileCache.revertPath()
}

renderSideView(): React.ReactNode {
const { viewState, connectDropTarget, canDrop, isOver } = this.props
const fileCache = viewState.getVisibleCache()
const appState = this.injected.appState
const active = appState.getViewFromCache(fileCache).isActive
const dropAndOver = isOver && canDrop
const divId = 'view_' + viewState.viewId

const activeClass = classNames('sideview', {
active: active,
inactive: !active,
hidden: this.props.hide,
dropTarget: dropAndOver,
notDropTarget: isOver && !canDrop,
})

const needLogin = fileCache.status === 'login'
const busy = fileCache.status === 'busy'
const dropOverlayActive = isOver
const dropOverlayIcon = isOver && !canDrop ? 'cross' : 'import'

return connectDropTarget(
<div id={divId} className={activeClass}>
{needLogin && (
<LoginDialog isOpen={needLogin} onValidation={this.onValidation} onClose={this.onClose} />
)}
<TabList></TabList>
<Toolbar active={active && !busy} onPaste={this.props.onPaste} />
<FileTable hide={this.props.hide} />
<Statusbar />
<Overlay active={busy}>
<Spinner />
</Overlay>
<Overlay active={dropOverlayActive}>
<Icon icon={dropOverlayIcon} size={80} color="#d9dde0" />
</Overlay>
</div>,
)
}

onDrop(item: DraggedObject /*| DataTransfer*/) {
const { appState, viewState } = this.injected
appState.drop(item, viewState.getVisibleCache())
debugger
}

render(): React.ReactNode {
const { viewState } = this.props

return <Provider viewState={viewState}>{this.renderSideView()}</Provider>
}
},
),
)

const SideViewDD = DropTarget<InjectedProps>('file', fileTarget, collect)(SideViewClass)

const SideView = withTranslation()(SideViewDD)

export { SideView }
const fileCache = viewState.getVisibleCache()
const active = appState.getViewFromCache(fileCache).isActive
const dropAndOver = isOver && canDrop
const divId = 'view_' + viewState.viewId

const activeClass = classNames('sideview', {
active,
inactive: !active,
hidden: hide,
dropTarget: dropAndOver,
notDropTarget: isOver && !canDrop,
})

const needLogin = fileCache.status === 'login'
const busy = fileCache.status === 'busy'
const dropOverlayIcon = isOver && !canDrop ? 'cross' : 'import'
const dropOverlayActive = isOver

return (
<Provider viewState={viewState}>
<div ref={drop} id={divId} className={activeClass}>
{needLogin && <LoginDialog isOpen={needLogin} onValidation={onValidation} onClose={onClose} />}
<TabList></TabList>
<Toolbar active={active && !busy} onPaste={onPaste} />
<FileTable hide={hide} />
<Statusbar />
<Overlay id={`files-loader-${viewState.viewId}`} active={busy}>
<Spinner />
</Overlay>
<Overlay active={dropOverlayActive}>
<Icon icon={dropOverlayIcon} size={80} color="#d9dde0" />
</Overlay>
</div>
</Provider>
)
})
4 changes: 1 addition & 3 deletions src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,8 @@ export const ToolbarClass = inject(
></Button>
)
const intent = status === -1 ? 'danger' : 'none'
const count = selected.length

const isRoot = fileCache.isRoot()
console.log(fileCache)

return (
<HotkeysTarget2 hotkeys={this.hotkeys}>
<ControlGroup className="toolbar">
Expand Down
Loading

0 comments on commit e68206c

Please sign in to comment.