export const formatFileSize = (size?: number) => {
if (typeof size === "number") {
+ if (size === 0) { return "0 B"; }
+
for (const { base, unit } of FILE_SIZES) {
if (size >= base) {
return `${(size / base).toFixed()} ${unit}`;
<span className={classes.cardHeaderContentTitle}>Files</span>
<SearchInput
value={searchValue}
+ label='Search files'
onSearch={setSearchValue} />
</div>
}
items: T[];
itemsAvailable: number;
columns: DataColumns<T>;
+ searchLabel?: string;
searchValue: string;
rowsPerPage: number;
rowsPerPageOptions: number[];
render() {
const {
columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
- rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
+ rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
paperKey, fetchMode, currentItemUuid, title
<Grid container justify="space-between" wrap="nowrap" alignItems="center">
<div className={classes.searchBox}>
{!hideSearchInput && <SearchInput
+ label={searchLabel}
value={searchValue}
onSearch={onSearch} />}
</div>
interface SearchInputDataProps {
value: string;
+ label?: string;
}
interface SearchInputActionProps {
interface SearchInputState {
value: string;
+ label: string;
}
export const DEFAULT_SEARCH_DEBOUNCE = 1000;
export const SearchInput = withStyles(styles)(
class extends React.Component<SearchInputProps> {
state: SearchInputState = {
- value: ""
+ value: "",
+ label: ""
};
timeout: number;
render() {
return <form onSubmit={this.handleSubmit}>
<FormControl>
- <InputLabel>Search files</InputLabel>
+ <InputLabel>{this.state.label}</InputLabel>
<Input
type="text"
value={this.state.value}
onChange={this.handleChange}
endAdornment={
<InputAdornment position="end">
- <Tooltip title='Search files'>
+ <Tooltip title='Search'>
<IconButton
onClick={this.handleSubmit}>
<SearchIcon />
}
componentDidMount() {
- this.setState({ value: this.props.value });
+ this.setState({
+ value: this.props.value,
+ label: this.props.label || 'Search'
+ });
}
componentWillReceiveProps(nextProps: SearchInputProps) {
select?: string[];
distinct?: boolean;
count?: string;
+ includeOldVersions?: boolean;
}
export interface ListResults<T> {
list(args: ListArguments = {}): Promise<ListResults<T>> {
const { filters, order, ...other } = args;
const params = {
- ...other,
+ ...CommonService.mapKeys(_.snakeCase)(other),
filters: filters ? `[${filters}]` : undefined,
order: order ? order : undefined
};
import { CollectionResource } from "~/models/collection";
import { ProjectResource } from "~/models/project";
import { ProcessResource } from "~/models/process";
+import { WorkflowResource } from "~/models/workflow";
import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
import { ApiActions } from "~/services/api/api-actions";
import { GroupResource } from "~/models/group";
export type GroupContentsResource =
CollectionResource |
ProjectResource |
- ProcessResource;
+ ProcessResource |
+ WorkflowResource;
export class GroupsService<T extends GroupResource = GroupResource> extends TrashableResourceService<T> {
export enum GroupContentsResourcePrefix {
COLLECTION = "collections",
PROJECT = "groups",
- PROCESS = "container_requests"
+ PROCESS = "container_requests",
+ WORKFLOW = "workflows"
}
filters: new FilterBuilder()
.addEqual('portable_data_hash', contentAddress)
.addILike("name", dataExplorer.searchValue)
- .getFilters()
+ .getFilters(),
+ includeOldVersions: true
});
const userUuids = response.items.map(it => {
if (extractUuidKind(it.ownerUuid) === ResourceKind.USER) {
import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox } from '@material-ui/core';
import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
import { ResourceKind, TrashableResource } from '~/models/resource';
-import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, WorkflowIcon, ShareIcon } from '~/components/icon/icon';
+import { ProjectIcon, CollectionIcon, ProcessIcon, DefaultIcon, ShareIcon, CollectionOldVersionIcon, WorkflowIcon } from '~/components/icon/icon';
import { formatDate, formatFileSize, formatTime } from '~/common/formatters';
import { resourceLabel } from '~/common/labels';
import { connect, DispatchProp } from 'react-redux';
import { CollectionResource } from '~/models/collection';
import { IllegalNamingWarning } from '~/components/warning/warning';
-const renderName = (dispatch: Dispatch, item: { name: string; uuid: string, kind: string }) =>
+const renderName = (dispatch: Dispatch, item: GroupContentsResource) =>
<Grid container alignItems="center" wrap="nowrap" spacing={16}>
<Grid item>
- {renderIcon(item.kind)}
+ {renderIcon(item)}
</Grid>
<Grid item>
<Typography color="primary" style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
export const ResourceName = connect(
(state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
- return resource || { name: '', uuid: '', kind: '' };
- })((resource: { name: string; uuid: string, kind: string } & DispatchProp<any>) => renderName(resource.dispatch, resource));
+ return resource;
+ })((resource: GroupContentsResource & DispatchProp<any>) => renderName(resource.dispatch, resource));
-const renderIcon = (kind: string) => {
- switch (kind) {
+const renderIcon = (item: GroupContentsResource) => {
+ switch (item.kind) {
case ResourceKind.PROJECT:
return <ProjectIcon />;
case ResourceKind.COLLECTION:
- return <CollectionIcon />;
+ if (item.uuid === item.currentVersionUuid) {
+ return <CollectionIcon />;
+ }
+ return <CollectionOldVersionIcon />;
case ResourceKind.PROCESS:
return <ProcessIcon />;
case ResourceKind.WORKFLOW:
return <Typography noWrap style={{ minWidth: '100px' }}>{formatDate(date)}</Typography>;
};
-const renderWorkflowName = (item: { name: string; uuid: string, kind: string, ownerUuid: string }) =>
+const renderWorkflowName = (item: WorkflowResource) =>
<Grid container alignItems="center" wrap="nowrap" spacing={16}>
<Grid item>
- {renderIcon(item.kind)}
+ {renderIcon(item)}
</Grid>
<Grid item>
<Typography color="primary" style={{ width: '100px' }}>
export const ResourceWorkflowName = connect(
(state: RootState, props: { uuid: string }) => {
const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
- return resource || { name: '', uuid: '', kind: '', ownerUuid: '' };
+ return resource;
})(renderWorkflowName);
const getPublicUuid = (uuidPrefix: string) => {
return <CollectionIcon className={className} />;
}
- getDetails() {
- return <CollectionDetailsAttributes item={this.item} twoCol={false} />;
+ getTabLabels() {
+ return ['Details', 'Versions'];
+ }
+
+ getDetails(tabNumber: number) {
+ switch (tabNumber) {
+ case 0:
+ return this.getCollectionInfo();
+ case 1:
+ return this.getVersionBrowser();
+ default:
+ return <div />;
+ }
+ }
+
+ private getCollectionInfo() {
+ return <CollectionDetailsAttributes twoCol={false} item={this.item} />;
+ }
+
+ private getVersionBrowser() {
+ return <div />;
}
}
return this.item.name || 'Projects';
}
- abstract getIcon(className?: string): React.ReactElement<any>;
- abstract getDetails(): React.ReactElement<any>;
-
- getActivity(): React.ReactElement<any> {
- return <div />;
+ getTabLabels(): string[] {
+ return ['Details'];
}
+
+ abstract getIcon(className?: string): React.ReactElement<any>;
+ abstract getDetails(tabNr?: number): React.ReactElement<any>;
}
</Grid>
<Grid item>
<Tabs value={tabsValue} onChange={this.handleChange}>
- <Tab disableRipple label="Details" />
- <Tab disableRipple label="Activity" disabled />
+ { item.getTabLabels().map((tabLabel, idx) =>
+ <Tab key={`tab-label-${idx}`} disableRipple label={tabLabel} />)
+ }
</Tabs>
</Grid>
<Grid item xs className={this.props.classes.tabContainer} >
- {tabsValue === 0
- ? item.getDetails()
- : null}
+ {tabsValue !== undefined
+ ? item.getDetails(tabsValue)
+ : null}
</Grid>
</Grid >;
}
getDetails() {
return <div>
<DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
- <DetailsAttribute label='Size' value='---' />
<DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid} />
- {/* Missing attr */}
<DetailsAttribute label='Status' value={this.item.state} />
<DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
- {/* Missing attrs */}
<DetailsAttribute label='Started at' value={formatDate(this.item.createdAt)} />
<DetailsAttribute label='Finished at' value={formatDate(this.item.expiresAt)} />
withStyles(styles)(
({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
<DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
- {/* Missing attr */}
- <DetailsAttribute label='Size' value='---' />
<DetailsAttribute label='Owner' linkToUuid={project.ownerUuid} lowercaseValue={true} />
<DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
<DetailsAttribute label='Created at' value={formatDate(project.createdAt)} />
<DetailsAttribute label='Project UUID' linkToUuid={project.uuid} value={project.uuid} />
- {/* Missing attr */}
- {/*<DetailsAttribute label='File size' value='1.4 GB' />*/}
<DetailsAttribute label='Description'>
{project.description ?
<RichTextEditorLink
import { DataColumns } from '~/components/data-table/data-table';
import { SortDirection } from '~/components/data-table/data-column';
import { createTree } from '~/models/tree';
-import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate } from '~/views-components/data-explorer/renderers';
+import { ResourceName, ResourceOwnerName, ResourceLastModifiedDate, ResourceStatus } from '~/views-components/data-explorer/renderers';
type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link';
enum CollectionContentAddressPanelColumnNames {
COLLECTION_WITH_THIS_ADDRESS = "Collection with this address",
+ STATUS = "Status",
LOCATION = "Location",
LAST_MODIFIED = "Last modified"
}
filters: createTree(),
render: uuid => <ResourceName uuid={uuid} />
},
+ {
+ name: CollectionContentAddressPanelColumnNames.STATUS,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceStatus uuid={uuid} />
+ },
{
name: CollectionContentAddressPanelColumnNames.LOCATION,
selected: true,
</Button>
<DataExplorer
id={COLLECTIONS_CONTENT_ADDRESS_PANEL_ID}
+ hideSearchInput
onRowClick={this.props.onItemClick}
onRowDoubleClick={this.props.onItemDoubleClick}
onContextMenu={this.props.onContextMenu}
this.props.dispatch<any>(deleteCollectionTag(key, value));
}
- openCollectionDetails = () => {
+ openCollectionDetails = (e: React.MouseEvent<HTMLElement>) => {
const { item } = this.props;
if (item) {
+ e.stopPropagation();
this.props.dispatch(openDetailsPanel(item.uuid));
}
}
},
{
name: FavoritePanelColumnNames.OWNER,
- selected: true,
+ selected: false,
configurable: true,
filters: createTree(),
render: uuid => <ResourceOwner uuid={uuid} />
import { ContainerRequestState } from '~/models/container-request';
import { SortDirection } from '~/components/data-table/data-column';
import { ResourceKind, Resource, EditableResource } from '~/models/resource';
-import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
+import {
+ ResourceFileSize,
+ ResourceLastModifiedDate,
+ ProcessStatus,
+ ResourceType,
+ ResourceOwner
+} from '~/views-components/data-explorer/renderers';
import { ProjectIcon } from '~/components/icon/icon';
import { ResourceName } from '~/views-components/data-explorer/renderers';
import { ResourcesState, getResourceWithEditableStatus } from '~/store/resources/resources';
},
{
name: ProjectPanelColumnNames.OWNER,
- selected: true,
+ selected: false,
configurable: true,
filters: createTree(),
render: uuid => <ResourceOwner uuid={uuid} />
ResourceFileSize,
ResourceLastModifiedDate,
ResourceType,
- ResourceName
+ ResourceName,
+ ResourceOwner
} from '~/views-components/data-explorer/renderers';
import { PublicFavoriteIcon } from '~/components/icon/icon';
import { Dispatch } from 'redux';
filters: getSimpleObjectTypeFilters(),
render: uuid => <ResourceType uuid={uuid} />
},
+ {
+ name: PublicFavoritePanelColumnNames.OWNER,
+ selected: false,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceOwner uuid={uuid} />
+ },
{
name: PublicFavoritePanelColumnNames.FILE_SIZE,
selected: true,