Merge branch 'master' into 14160-default-data-explorer-view-improvements
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 6 Sep 2018 06:53:22 +0000 (08:53 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 6 Sep 2018 06:53:22 +0000 (08:53 +0200)
refs #14160

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

src/components/data-explorer/data-explorer.test.tsx
src/components/data-explorer/data-explorer.tsx
src/components/data-table-default-view/data-table-default-view.tsx [new file with mode: 0644]
src/components/data-table/data-table.test.tsx
src/components/data-table/data-table.tsx
src/components/panel-default-view/panel-default-view.tsx [new file with mode: 0644]
src/views/favorite-panel/favorite-panel.tsx
src/views/project-panel/project-panel.tsx
src/views/trash-panel/trash-panel.tsx

index 882c178be5ef5550629412f5116a7ab3f4e6d50f..d74b5319d789b743bfa6878bd6e359839cdbe16d 100644 (file)
@@ -73,15 +73,6 @@ describe("<DataExplorer />", () => {
         expect(onRowClick).toHaveBeenCalledWith("rowClick");
     });
 
-    it("does not render <DataTable/> if there is no items", () => {
-        const dataExplorer = mount(<DataExplorer
-            {...mockDataExplorerProps()}
-            items={[]}
-            onSetColumns={jest.fn()} />);
-        expect(dataExplorer.find(DataTable)).toHaveLength(0);
-        expect(dataExplorer.find(DefaultView)).toHaveLength(1);
-    });
-
     it("communicates with <TablePagination/>", () => {
         const onChangePage = jest.fn();
         const onChangeRowsPerPage = jest.fn();
index ea400b100edfac2b8d06a8ea7e9fb45b4fef3e61..1535fde2b67a8097df409db233d89d3ec1ec7903 100644 (file)
@@ -11,10 +11,8 @@ import { DataColumn, SortDirection } from "../data-table/data-column";
 import { DataTableFilterItem } from '../data-table-filters/data-table-filters';
 import { SearchInput } from '../search-input/search-input';
 import { ArvadosTheme } from "~/common/custom-theme";
-import { DefaultView } from '../default-view/default-view';
-import { IconType } from '../icon/icon';
 
-type CssRules = 'searchBox' | "toolbar" | 'defaultRoot' | 'defaultMessage' | 'defaultIcon';
+type CssRules = 'searchBox' | "toolbar";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     searchBox: {
@@ -23,19 +21,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     toolbar: {
         paddingTop: theme.spacing.unit * 2
     },
-    defaultRoot: {
-        position: 'absolute',
-        width: '80%',
-        left: '50%',
-        top: '50%',
-        transform: 'translate(-50%, -50%)'
-    },
-    defaultMessage: {
-        fontSize: '1.75rem',
-    },
-    defaultIcon: {
-        fontSize: '6rem'
-    }
 });
 
 interface DataExplorerDataProps<T> {
@@ -46,9 +31,8 @@ interface DataExplorerDataProps<T> {
     rowsPerPage: number;
     rowsPerPageOptions: number[];
     page: number;
-    defaultIcon: IconType;
-    defaultMessages: string[];
     contextMenuColumn: boolean;
+    dataTableDefaultView?: React.ReactNode;
 }
 
 interface DataExplorerActionProps<T> {
@@ -78,54 +62,46 @@ export const DataExplorer = withStyles(styles)(
             const {
                 columns, onContextMenu, onFiltersChange, onSortToggle, extractKey,
                 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
-                items, itemsAvailable, onRowClick, onRowDoubleClick, defaultIcon, defaultMessages, classes
+                items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
+                dataTableDefaultView
             } = this.props;
-            return <div>
-                { items.length > 0 ? (
-                    <Paper>
-                        <Toolbar className={classes.toolbar}>
-                            <Grid container justify="space-between" wrap="nowrap" alignItems="center">
-                                <div className={classes.searchBox}>
-                                    <SearchInput
-                                        value={searchValue}
-                                        onSearch={onSearch}/>
-                                </div>
-                                <ColumnSelector
-                                    columns={columns}
-                                    onColumnToggle={onColumnToggle}/>
-                            </Grid>
-                        </Toolbar>
-                        <DataTable
-                            columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
-                            items={items}
-                            onRowClick={(_, item: T) => onRowClick(item)}
-                            onContextMenu={onContextMenu}
-                            onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
-                            onFiltersChange={onFiltersChange}
-                            onSortToggle={onSortToggle}
-                            extractKey={extractKey}/>
-                        <Toolbar>
-                            <Grid container justify="flex-end">
-                                <TablePagination
-                                    count={itemsAvailable}
-                                    rowsPerPage={rowsPerPage}
-                                    rowsPerPageOptions={rowsPerPageOptions}
-                                    page={this.props.page}
-                                    onChangePage={this.changePage}
-                                    onChangeRowsPerPage={this.changeRowsPerPage}
-                                    component="div" />
-                            </Grid>
-                        </Toolbar>
-                    </Paper>
-                ) : (
-                    <DefaultView
-                        classRoot={classes.defaultRoot}
-                        icon={defaultIcon}
-                        classIcon={classes.defaultIcon}
-                        messages={defaultMessages}
-                        classMessage={classes.defaultMessage} />
-                )}
-            </div>;
+            return <Paper>
+                <Toolbar className={classes.toolbar}>
+                    <Grid container justify="space-between" wrap="nowrap" alignItems="center">
+                        <div className={classes.searchBox}>
+                            <SearchInput
+                                value={searchValue}
+                                onSearch={onSearch} />
+                        </div>
+                        <ColumnSelector
+                            columns={columns}
+                            onColumnToggle={onColumnToggle} />
+                    </Grid>
+                </Toolbar>
+                <DataTable
+                    columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
+                    items={items}
+                    onRowClick={(_, item: T) => onRowClick(item)}
+                    onContextMenu={onContextMenu}
+                    onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
+                    onFiltersChange={onFiltersChange}
+                    onSortToggle={onSortToggle}
+                    extractKey={extractKey}
+                    defaultView={dataTableDefaultView}
+                />
+                <Toolbar>
+                    <Grid container justify="flex-end">
+                        <TablePagination
+                            count={itemsAvailable}
+                            rowsPerPage={rowsPerPage}
+                            rowsPerPageOptions={rowsPerPageOptions}
+                            page={this.props.page}
+                            onChangePage={this.changePage}
+                            onChangeRowsPerPage={this.changeRowsPerPage}
+                            component="div" />
+                    </Grid>
+                </Toolbar>
+            </Paper>;
         }
 
         changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
@@ -139,7 +115,7 @@ export const DataExplorer = withStyles(styles)(
         renderContextMenuTrigger = (item: T) =>
             <Grid container justify="flex-end">
                 <IconButton onClick={event => this.props.onContextMenu(event, item)}>
-                    <MoreVertIcon/>
+                    <MoreVertIcon />
                 </IconButton>
             </Grid>
 
diff --git a/src/components/data-table-default-view/data-table-default-view.tsx b/src/components/data-table-default-view/data-table-default-view.tsx
new file mode 100644 (file)
index 0000000..9f674b9
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { DefaultViewDataProps, DefaultView } from '~/components/default-view/default-view';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { DetailsIcon } from '~/components/icon/icon';
+
+type CssRules = 'classRoot';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    classRoot: {
+        marginTop: theme.spacing.unit * 4,
+        marginBottom: theme.spacing.unit * 4,
+    },
+});
+type DataTableDefaultViewDataProps = Partial<Pick<DefaultViewDataProps, 'icon' | 'messages'>>;
+type DataTableDefaultViewProps = DataTableDefaultViewDataProps & WithStyles<CssRules>;
+
+export const DataTableDefaultView = withStyles(styles)(
+    ({ classes, ...props }: DataTableDefaultViewProps) => {
+        const icon = props.icon || DetailsIcon;
+        const messages = props.messages || ['No items found'];
+        return <DefaultView {...classes} {...{ icon, messages }} />;
+    });
index 1201dcb07316a45117e0b5300484156d4d52c34f..16957b4031fe933b10f0883b490f7bd20d1ed1d7 100644 (file)
@@ -9,6 +9,7 @@ import * as Adapter from "enzyme-adapter-react-16";
 import { DataTable, DataColumns } from "./data-table";
 import { DataTableFilters } from "../data-table-filters/data-table-filters";
 import { SortDirection, createDataColumn } from "./data-column";
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 
 configure({ adapter: new Adapter() });
 
@@ -201,4 +202,24 @@ describe("<DataTable />", () => {
         dataTable.find(DataTableFilters).prop("onChange")([]);
         expect(onFiltersChange).toHaveBeenCalledWith([], columns[0]);
     });
+
+    it("shows default view if there is no items", () => {
+        const columns: DataColumns<string> = [
+            createDataColumn({
+                name: "Column 1",
+                render: () => <span />,
+                selected: true,
+                configurable: true
+            }),
+        ];
+        const dataTable = mount(<DataTable
+            columns={columns}
+            items={[]}
+            onFiltersChange={jest.fn()}
+            onRowClick={jest.fn()}
+            onRowDoubleClick={jest.fn()}
+            onContextMenu={jest.fn()}
+            onSortToggle={jest.fn()} />);
+        expect(dataTable.find(DataTableDefaultView)).toHaveLength(1);
+    });
 });
index 5a6f9e5a5b5337abce7b07d738206cbd84d56c78..db7b484a09660241e9170c9b83e31ee8899a6f69 100644 (file)
@@ -5,7 +5,8 @@
 import * as React from 'react';
 import { Table, TableBody, TableRow, TableCell, TableHead, TableSortLabel, StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core';
 import { DataColumn, SortDirection } from './data-column';
-import { DataTableFilters,  DataTableFilterItem } from "../data-table-filters/data-table-filters";
+import { DataTableFilters, DataTableFilterItem } from "../data-table-filters/data-table-filters";
+import { DataTableDefaultView } from '../data-table-default-view/data-table-default-view';
 
 export type DataColumns<T, F extends DataTableFilterItem = DataTableFilterItem> = Array<DataColumn<T, F>>;
 
@@ -18,22 +19,26 @@ export interface DataTableDataProps<T> {
     onSortToggle: (column: DataColumn<T>) => void;
     onFiltersChange: (filters: DataTableFilterItem[], column: DataColumn<T>) => void;
     extractKey?: (item: T) => React.Key;
+    defaultView?: React.ReactNode;
 }
 
-type CssRules = "tableBody" | "tableContainer" | "noItemsInfo";
+type CssRules = "tableBody" | "root" | "content" | "noItemsInfo";
 
 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
-    tableContainer: {
+    root: {
         overflowX: 'auto',
         overflowY: 'hidden'
     },
+    content: {
+        display: 'inline-block',
+    },
     tableBody: {
         background: theme.palette.background.paper
     },
     noItemsInfo: {
         textAlign: "center",
         padding: theme.spacing.unit
-    }
+    },
 });
 
 type DataTableProps<T> = DataTableDataProps<T> & WithStyles<CssRules>;
@@ -42,21 +47,29 @@ export const DataTable = withStyles(styles)(
     class Component<T> extends React.Component<DataTableProps<T>> {
         render() {
             const { items, classes } = this.props;
-            return <div
-                className={classes.tableContainer}>
-                <Table>
-                    <TableHead>
-                        <TableRow>
-                            {this.mapVisibleColumns(this.renderHeadCell)}
-                        </TableRow>
-                    </TableHead>
-                    <TableBody className={classes.tableBody}>
-                        {items.map(this.renderBodyRow)}
-                    </TableBody>
-                </Table>
+            return <div className={classes.root}>
+                <div className={classes.content}>
+                    <Table>
+                        <TableHead>
+                            <TableRow>
+                                {this.mapVisibleColumns(this.renderHeadCell)}
+                            </TableRow>
+                        </TableHead>
+                        <TableBody className={classes.tableBody}>
+                            {items.map(this.renderBodyRow)}
+                        </TableBody>
+                    </Table>
+                    {items.length === 0 && this.renderNoItemsPlaceholder()}
+                </div>
             </div>;
         }
 
+        renderNoItemsPlaceholder = () => {
+            return this.props.defaultView
+                ? this.props.defaultView
+                : <DataTableDefaultView />;
+        }
+
         renderHeadCell = (column: DataColumn<T>, index: number) => {
             const { name, key, renderHeader, filters, sortDirection } = column;
             const { onSortToggle, onFiltersChange } = this.props;
@@ -94,7 +107,7 @@ export const DataTable = withStyles(styles)(
                 key={extractKey ? extractKey(item) : index}
                 onClick={event => onRowClick && onRowClick(event, item)}
                 onContextMenu={this.handleRowContextMenu(item)}
-                onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item) }>
+                onDoubleClick={event => onRowDoubleClick && onRowDoubleClick(event, item)}>
                 {this.mapVisibleColumns((column, index) => (
                     <TableCell key={column.key || index}>
                         {column.render(item)}
diff --git a/src/components/panel-default-view/panel-default-view.tsx b/src/components/panel-default-view/panel-default-view.tsx
new file mode 100644 (file)
index 0000000..18be3ac
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { DefaultViewDataProps, DefaultView } from '~/components/default-view/default-view';
+
+type CssRules = 'classRoot' | 'classIcon' | 'classMessage';
+
+const styles: StyleRulesCallback<CssRules> = () => ({
+    classRoot: {
+        position: 'absolute',
+        width: '80%',
+        left: '50%',
+        top: '50%',
+        transform: 'translate(-50%, -50%)'
+    },
+    classMessage: {
+        fontSize: '1.75rem',
+    },
+    classIcon: {
+        fontSize: '6rem'
+    }
+});
+
+type PanelDefaultViewProps = Pick<DefaultViewDataProps, 'icon' | 'messages'> & WithStyles<CssRules>;
+
+export const PanelDefaultView = withStyles(styles)(
+    ({ classes, ...props }: PanelDefaultViewProps) =>
+        <DefaultView {...classes} {...props} />);
index 803d8002a365105c2691648e2a5f000cbd3be254..9f35ecd6c27e26d5159656eafb369993bda33ad9 100644 (file)
@@ -28,6 +28,10 @@ import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-
 import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
 import { navigateTo } from '~/store/navigation/navigation-action';
 import { ContainerRequestState } from "~/models/container-request";
+import { FavoritesState } from '../../store/favorites/favorites-reducer';
+import { RootState } from '~/store/store';
+import { PanelDefaultView } from '~/components/panel-default-view/panel-default-view';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 
 type CssRules = "toolbar" | "button";
 
@@ -124,7 +128,7 @@ export const favoritePanelColumns: DataColumns<string, FavoritePanelFilter> = [
 ];
 
 interface FavoritePanelDataProps {
-    currentItemId: string;
+    favorites: FavoritesState;
 }
 
 interface FavoritePanelActionProps {
@@ -133,6 +137,9 @@ interface FavoritePanelActionProps {
     onDialogOpen: (ownerUuid: string) => void;
     onItemDoubleClick: (item: string) => void;
 }
+const mapStateToProps = ({ favorites }: RootState): FavoritePanelDataProps => ({
+    favorites
+});
 
 const mapDispatchToProps = (dispatch: Dispatch): FavoritePanelActionProps => ({
     onContextMenu: (event, resourceUuid) => {
@@ -160,17 +167,26 @@ type FavoritePanelProps = FavoritePanelDataProps & FavoritePanelActionProps & Di
     & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
 
 export const FavoritePanel = withStyles(styles)(
-    connect(undefined, mapDispatchToProps)(
+    connect(mapStateToProps, mapDispatchToProps)(
         class extends React.Component<FavoritePanelProps> {
             render() {
-                return <DataExplorer
-                    id={FAVORITE_PANEL_ID}
-                    onRowClick={this.props.onItemClick}
-                    onRowDoubleClick={this.props.onItemDoubleClick}
-                    onContextMenu={this.props.onContextMenu}
-                    defaultIcon={FavoriteIcon}
-                    defaultMessages={['Your favorites list is empty.']}
-                    contextMenuColumn={true}/>;
+                return this.hasAnyFavorites()
+                    ? <DataExplorer
+                        id={FAVORITE_PANEL_ID}
+                        onRowClick={this.props.onItemClick}
+                        onRowDoubleClick={this.props.onItemDoubleClick}
+                        onContextMenu={this.props.onContextMenu}
+                        contextMenuColumn={true}
+                        dataTableDefaultView={<DataTableDefaultView icon={FavoriteIcon}/>} />
+                    : <PanelDefaultView
+                        icon={FavoriteIcon}
+                        messages={['Your favorites list is empty.']} />;
+            }
+
+            hasAnyFavorites = () => {
+                return Object
+                    .keys(this.props.favorites)
+                    .find(uuid => this.props.favorites[uuid]);
             }
         }
     )
index 2573f81813a920256cc9a0801e2af8f2dd7eb611..aa361bc9cb47431acaed8dfc8967e513d43abbca 100644 (file)
@@ -12,7 +12,7 @@ import { RootState } from '~/store/store';
 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
 import { ContainerRequestState } from '~/models/container-request';
 import { SortDirection } from '~/components/data-table/data-column';
-import { ResourceKind } from '~/models/resource';
+import { ResourceKind, Resource } from '~/models/resource';
 import { resourceLabel } from '~/common/labels';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
@@ -25,8 +25,11 @@ import { ProjectResource } from '~/models/project';
 import { navigateTo } from '~/store/navigation/navigation-action';
 import { getProperty } from '~/store/properties/properties';
 import { PROJECT_PANEL_CURRENT_UUID } from '~/store/project-panel/project-panel-action';
-import { openCollectionCreateDialog } from '../../store/collections/collection-create-actions';
+import { openCollectionCreateDialog } from '~/store/collections/collection-create-actions';
 import { openProjectCreateDialog } from '~/store/projects/project-create-actions';
+import { filterResources } from '~/store/resources/resources';
+import { PanelDefaultView } from '~/components/panel-default-view/panel-default-view';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 
 type CssRules = 'root' | "toolbar" | "button";
 
@@ -157,17 +160,31 @@ export const ProjectPanel = withStyles(styles)(
                             New project
                         </Button>
                     </div>
-                    <DataExplorer
-                        id={PROJECT_PANEL_ID}
-                        onRowClick={this.handleRowClick}
-                        onRowDoubleClick={this.handleRowDoubleClick}
-                        onContextMenu={this.handleContextMenu}
-                        defaultIcon={ProjectIcon}
-                        defaultMessages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']}
-                        contextMenuColumn={true}/>
+                    {this.hasAnyItems()
+                        ? <DataExplorer
+                            id={PROJECT_PANEL_ID}
+                            onRowClick={this.handleRowClick}
+                            onRowDoubleClick={this.handleRowDoubleClick}
+                            onContextMenu={this.handleContextMenu}
+                            contextMenuColumn={true}
+                            dataTableDefaultView={<DataTableDefaultView icon={ProjectIcon}/>} />
+                        : <PanelDefaultView
+                            icon={ProjectIcon}
+                            messages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']} />
+                    }
+
                 </div>;
             }
 
+            hasAnyItems = () => {
+                const resources = filterResources(this.isCurrentItemChild)(this.props.resources);
+                return resources.length > 0;
+            }
+
+            isCurrentItemChild = (resource: Resource) => {
+                return resource.ownerUuid === this.props.currentItemId;
+            }
+
             handleNewProjectClick = () => {
                 this.props.dispatch<any>(openProjectCreateDialog(this.props.currentItemId));
             }
index 08df05c247b61b8be75874ba87c4adbaa7834d71..a3664542eba60dde0742a58e7ed53b24e7dda5ff 100644 (file)
@@ -28,9 +28,11 @@ import {
 } from "~/views-components/data-explorer/renderers";
 import { navigateTo } from "~/store/navigation/navigation-action";
 import { loadDetailsPanel } from "~/store/details-panel/details-panel-action";
-import { toggleCollectionTrashed, toggleProjectTrashed, toggleTrashed } from "~/store/trash/trash-actions";
+import { toggleTrashed } from "~/store/trash/trash-actions";
 import { ContextMenuKind } from "~/views-components/context-menu/context-menu";
 import { Dispatch } from "redux";
+import { PanelDefaultView } from '~/components/panel-default-view/panel-default-view';
+import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
 
 type CssRules = "toolbar" | "button";
 
@@ -71,7 +73,7 @@ export const ResourceRestore =
                 ));
             }
         }}>
-            <RestoreFromTrashIcon/>
+            <RestoreFromTrashIcon />
         </IconButton>
     );
 
@@ -82,7 +84,7 @@ export const trashPanelColumns: DataColumns<string, TrashPanelFilter> = [
         configurable: true,
         sortDirection: SortDirection.ASC,
         filters: [],
-        render: uuid => <ResourceName uuid={uuid}/>,
+        render: uuid => <ResourceName uuid={uuid} />,
         width: "450px"
     },
     {
@@ -107,7 +109,7 @@ export const trashPanelColumns: DataColumns<string, TrashPanelFilter> = [
                 type: ResourceKind.PROJECT
             }
         ],
-        render: uuid => <ResourceType uuid={uuid}/>,
+        render: uuid => <ResourceType uuid={uuid} />,
         width: "125px"
     },
     {
@@ -143,7 +145,7 @@ export const trashPanelColumns: DataColumns<string, TrashPanelFilter> = [
         configurable: false,
         sortDirection: SortDirection.NONE,
         filters: [],
-        render: uuid => <ResourceRestore uuid={uuid}/>,
+        render: uuid => <ResourceRestore uuid={uuid} />,
         width: "50px"
     }
 ];
@@ -162,15 +164,23 @@ export const TrashPanel = withStyles(styles)(
     }))(
         class extends React.Component<TrashPanelProps> {
             render() {
-                return <DataExplorer
-                    id={TRASH_PANEL_ID}
-                    onRowClick={this.handleRowClick}
-                    onRowDoubleClick={this.handleRowDoubleClick}
-                    onContextMenu={this.handleContextMenu}
-                    defaultIcon={TrashIcon}
-                    defaultMessages={['Your trash list is empty.']}
-                    contextMenuColumn={false}/>
-                ;
+                return this.hasAnyTrashedResources()
+                    ? <DataExplorer
+                        id={TRASH_PANEL_ID}
+                        onRowClick={this.handleRowClick}
+                        onRowDoubleClick={this.handleRowDoubleClick}
+                        onContextMenu={this.handleContextMenu}
+                        contextMenuColumn={false}
+                        dataTableDefaultView={<DataTableDefaultView icon={TrashIcon}/>} />
+                    : <PanelDefaultView
+                        icon={TrashIcon}
+                        messages={['Your trash list is empty.']} />;
+            }
+
+            hasAnyTrashedResources = () => {
+                // TODO: implement check if there is anything in the trash,
+                //       without taking pagination into the account
+                return true;
             }
 
             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {