frontend/src/js/components/viewer/LazyTreeBrowser.tsx (133 lines of code) (raw):
import React, { useState } from 'react';
import { Resource, BasicResource } from '../../types/Resource';
import { getLastPart } from '../../util/stringUtils';
import DocumentIcon from 'react-icons/lib/ti/document';
import EmailIcon from 'react-icons/lib/md/email';
import TreeBrowser from '../UtilComponents/TreeBrowser';
import { ResourceBreadcrumbs } from '../ResourceBreadcrumbs';
import sortBy from 'lodash/sortBy';
import { Tree, TreeEntry, TreeLeaf, TreeNode } from '../../types/Tree';
import { getChildResource } from '../../actions/resources/getResource';
type PropTypes = {
rootResource: Resource,
descendantResources: {[uri: string]: BasicResource},
getChildResource: typeof getChildResource
}
function resourceToTreeEntry(rootResource: BasicResource, descendantResources: {[uri: string]: BasicResource}): TreeEntry<BasicResource> {
const resource = (descendantResources && descendantResources[rootResource.uri]) || rootResource;
const name = getLastPart(resource.display || decodeURIComponent(resource.uri), '/');
if (resource.children && resource.children.length) {
const childrenFoldersOnTop = sortBy(resource.children, child => !child.isExpandable);
return {
id: resource.uri,
name,
data: resource,
children: childrenFoldersOnTop.map(child => resourceToTreeEntry(child, descendantResources)),
}
} else {
return {
id: resource.uri,
name,
isExpandable: resource.isExpandable,
data: resource,
}
}
}
function treeFromResource(rootResource: BasicResource, descendantResources: {[uri: string]: BasicResource}): Tree<BasicResource> {
if (rootResource.children && rootResource.children.length) {
const childrenFoldersOnTop = sortBy(rootResource.children, child => !child.isExpandable);
return childrenFoldersOnTop.map(child => resourceToTreeEntry(child, descendantResources))
} else {
return [];
}
}
function renderIcon(resource: BasicResource) {
// Zips aren't viewable, so don't show a document icon that makes it seem like they are
if (resource.type === 'file' && resource.isExpandable) {
return null;
}
switch (resource.type) {
case 'file': return <DocumentIcon className='file-browser__icon'/>;
case 'email': return <EmailIcon className='file-browser__icon'/>;
default: return null;
}
}
export default function LazyTreeBrowser({ rootResource, descendantResources, getChildResource }: PropTypes
) {
// You can't currently perform any actions after selecting a resource but we
// highlight it anyway to avoid the UI feeling broken
const [focusedEntry, setFocusedEntry] = useState<TreeEntry<BasicResource> | null>(null);
const [expandedEntries, setExpandedEntries] = useState<TreeEntry<BasicResource>[]>([]);
const [selectedEntries, setSelectedEntries] = useState<TreeEntry<BasicResource>[]>([]);
function onExpandNode(node: TreeNode<BasicResource>) {
setExpandedEntries([...expandedEntries, node]);
}
function onCollapseNode(node: TreeNode<BasicResource>) {
setExpandedEntries(expandedEntries.filter(({ id }) => id !== node.id));
}
function onExpandLeaf(leaf: TreeLeaf<BasicResource>) {
getChildResource(leaf.id);
// The leaf will become a node (à la the ugly duckling)
// once its resource has been fetched, since it will have children.
// We want to make sure that node appears expanded.
setExpandedEntries([...expandedEntries, leaf]);
}
return (<React.Fragment>
<ResourceBreadcrumbs
className=""
childClass="lazy-tree-browser__filename"
resource={rootResource}
showParents={false}
showChildren={false}
lastSegmentOnly={false}
showCurrent={true}
/>
<TreeBrowser
rootId={rootResource.uri}
tree={treeFromResource(rootResource, descendantResources)}
onFocus={(entry, isMetaKeyHeld) => {
setSelectedEntries([entry]);
setFocusedEntry(entry);
}}
clearFocus={() => {}}
selectedEntries={selectedEntries}
focusedEntry={focusedEntry}
onMoveItems={() => {}}
onSelectLeaf={(leaf: TreeLeaf<BasicResource>) => {
const resource = leaf.data;
if (resource.isExpandable) {
onExpandLeaf(leaf);
} else {
window.open(`/resources/${resource.uri}`, '_blank');
}
}}
onClickColumn={() => {}}
columnsConfig={{
columns: [
{
name: 'files',
align: 'left',
style: {width: '100%'},
render: (n: TreeEntry<BasicResource>) => {
const name = n.name || '--';
const resource = n.data;
return <React.Fragment>
{renderIcon(resource)}
<span data-uri={`/resources/${resource.uri}`}>{name}</span>
{resource.type === 'email' && resource.isExpandable && <a href={`/resources/${resource.uri}`}>View email</a>}
</React.Fragment>;
},
sort: (a, b) => {
if (a.data.isExpandable !== b.data.isExpandable) {
return a.data.isExpandable ? -1 : 1;
}
return a.name.localeCompare(b.name);
}
}
],
sortDescending: false,
sortColumn: 'Name',
}}
expandedEntries={expandedEntries}
onExpandLeaf={onExpandLeaf}
onExpandNode={onExpandNode}
onCollapseNode={onCollapseNode}
onContextMenu={() => {}}
showColumnHeaders={false}
/>
</React.Fragment>);
}