--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface FileTreeData {
+ name: string;
+ type: string;
+ size?: number;
+}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { TreeItem } from "../tree/tree";
+import { ProjectIcon, MoreOptionsIcon } from "../icon/icon";
+import { Typography, IconButton, StyleRulesCallback, withStyles, WithStyles } from "@material-ui/core";
+import { formatFileSize } from "../../common/formatters";
+import { ListItemTextIcon } from "../list-item-text-icon/list-item-text-icon";
+import { FileTreeData } from "./file-tree-data";
+
+type CssRules = "root" | "spacer" | "sizeInfo" | "button";
+
+const fileTreeItemStyle: StyleRulesCallback<CssRules> = theme => ({
+ root: {
+ display: "flex",
+ alignItems: "center",
+ paddingRight: `${theme.spacing.unit}px`
+ },
+ spacer: {
+ flex: "1"
+ },
+ sizeInfo: {
+ marginRight: `${theme.spacing.unit * 3}px`
+ },
+ button: {
+ width: theme.spacing.unit * 3,
+ height: theme.spacing.unit * 3
+ }
+});
+
+export interface FileTreeItemProps {
+ item: TreeItem<FileTreeData>;
+ onMoreClick: (event: React.MouseEvent<any>, item: TreeItem<FileTreeData>) => void;
+}
+export const FileTreeItem = withStyles(fileTreeItemStyle)(
+ class extends React.Component<FileTreeItemProps & WithStyles<CssRules>> {
+ render() {
+ const { classes, item } = this.props;
+ return <div className={classes.root}>
+ <ListItemTextIcon
+ icon={ProjectIcon}
+ name={item.data.name} />
+ <div className={classes.spacer} />
+ <Typography
+ className={classes.sizeInfo}
+ variant="caption">{formatFileSize(item.data.size)}</Typography>
+ <IconButton
+ className={classes.button}
+ onClick={this.handleClick}>
+ <MoreOptionsIcon />
+ </IconButton>
+ </div >;
+ }
+
+ handleClick = (event: React.MouseEvent<any>) => {
+ this.props.onMoreClick(event, this.props.item);
+ }
+ });
+
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Tree, TreeItem, TreeItemStatus } from "../tree/tree";
+import { FileTreeData } from "./file-tree-data";
+import { FileTreeItem } from "./file-tree-item";
+
+export interface FileTreeProps {
+ items: Array<TreeItem<FileTreeData>>;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => void;
+ onSelectionToggle: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => void;
+ onCollapseToggle: (id: string, status: TreeItemStatus) => void;
+}
+
+export class FileTree extends React.Component<FileTreeProps> {
+ render() {
+ return <Tree
+ showSelection={true}
+ items={this.props.items}
+ disableRipple={true}
+ render={this.renderItem}
+ onContextMenu={this.handleContextMenu}
+ toggleItemActive={this.handleToggleActive}
+ toggleItemOpen={this.handleToggle}
+ onSelectionChange={this.handleSelectionChange} />;
+ }
+
+ handleContextMenu = (event: React.MouseEvent<any>, item: TreeItem<FileTreeData>) => {
+ event.stopPropagation();
+ this.props.onContextMenu(event, item);
+ }
+
+ handleToggle = (id: string, status: TreeItemStatus) => {
+ this.props.onCollapseToggle(id, status);
+ }
+
+ handleToggleActive = () => { return; };
+
+ handleSelectionChange = (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => {
+ event.stopPropagation();
+ this.props.onSelectionToggle(event, item);
+ }
+
+ renderItem = (item: TreeItem<FileTreeData>) =>
+ <FileTreeItem
+ item={item}
+ onMoreClick={this.handleContextMenu} />
+
+}