expect(Tree.getNode('Node 1')(newTree)).toEqual(initTreeNode({ id: 'Node 1', value: 'Value 1' }));
});
+ it('appends a subtree', () => {
+ const newTree = Tree.setNode(initTreeNode({ id: 'Node 1', value: 'Value 1' }))(tree);
+ const subtree = Tree.setNode(initTreeNode({ id: 'Node 2', value: 'Value 2' }))(Tree.createTree());
+ const mergedTree = Tree.appendSubtree('Node 1', subtree)(newTree);
+ expect(Tree.getNode('Node 1')(mergedTree)).toBeDefined();
+ expect(Tree.getNode('Node 2')(mergedTree)).toBeDefined();
+ });
+
it('adds new node reference to parent children', () => {
const newTree = pipe(
Tree.setNode(initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' })),
initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 2' }),
].reduce((tree, node) => Tree.setNode(node)(tree), tree);
const mappedTree = Tree.mapTreeValues<string, number>(value => parseInt(value.split(' ')[1], 10))(newTree);
- expect(Tree.getNode('Node 2')(mappedTree)).toEqual(initTreeNode({id: 'Node 2', parent: 'Node 1', value: 2 }));
+ expect(Tree.getNode('Node 2')(mappedTree)).toEqual(initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 2 }));
});
});
//
// SPDX-License-Identifier: AGPL-3.0
-import { pipe } from 'lodash/fp';
+import { pipe, map, reduce } from 'lodash/fp';
export type Tree<T> = Record<string, TreeNode<T>>;
export const TREE_ROOT_ID = '';
export const getNode = (id: string) => <T>(tree: Tree<T>): TreeNode<T> | undefined => tree[id];
+export const appendSubtree = <T>(id: string, subtree: Tree<T>) => (tree: Tree<T>) =>
+ pipe(
+ getNodeDescendants(''),
+ map(node => node.parent === '' ? { ...node, parent: id } : node),
+ reduce((newTree, node) => setNode(node)(newTree), tree)
+ )(subtree) as Tree<T>;
+
export const setNode = <T>(node: TreeNode<T>) => (tree: Tree<T>): Tree<T> => {
return pipe(
(tree: Tree<T>) => getNode(node.id)(tree) === node
import { getCommonResourceServiceError, CommonResourceServiceError } from '~/services/common-service/common-resource-service';
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
+import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
export const COLLECTION_COPY_FORM_NAME = 'collectionCopyFormName';
export const openCollectionCopyDialog = (resource: { name: string, uuid: string }) =>
(dispatch: Dispatch) => {
dispatch<any>(resetPickerProjectTree());
+ dispatch<any>(initProjectsTreePicker(COLLECTION_COPY_FORM_NAME));
const initialData: CopyFormDialogData = { name: `Copy of: ${resource.name}`, ownerUuid: '', uuid: resource.uuid };
dispatch<any>(initialize(COLLECTION_COPY_FORM_NAME, initialData));
dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_COPY_FORM_NAME, data: {} }));
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
+import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
export const COLLECTION_MOVE_FORM_NAME = 'collectionMoveFormName';
export const openMoveCollectionDialog = (resource: { name: string, uuid: string }) =>
(dispatch: Dispatch) => {
dispatch<any>(resetPickerProjectTree());
+ dispatch<any>(initProjectsTreePicker(COLLECTION_MOVE_FORM_NAME));
dispatch(initialize(COLLECTION_MOVE_FORM_NAME, resource));
dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_MOVE_FORM_NAME, data: {} }));
};
import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
import { getCommonResourceServiceError, CommonResourceServiceError } from '~/services/common-service/common-resource-service';
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
+import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
export const COLLECTION_PARTIAL_COPY_FORM_NAME = 'COLLECTION_PARTIAL_COPY_DIALOG';
};
dispatch(initialize(COLLECTION_PARTIAL_COPY_FORM_NAME, initialData));
dispatch<any>(resetPickerProjectTree());
+ dispatch<any>(initProjectsTreePicker(COLLECTION_PARTIAL_COPY_FORM_NAME));
dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME, data: {} }));
}
};
// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from '~/common/unionize';
+import { RootState } from '~/store/store';
+import { Dispatch } from 'redux';
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { getResource } from '~/store/resources/resources';
+import { ProjectResource } from "~/models/project";
+import { ServiceRepository } from '~/services/services';
+import { TagProperty } from '~/models/tag';
+import { startSubmit, stopSubmit } from 'redux-form';
+import { resourcesActions } from '~/store/resources/resources-actions';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+
+export const SLIDE_TIMEOUT = 500;
export const detailsPanelActions = unionize({
TOGGLE_DETAILS_PANEL: ofType<{}>(),
export type DetailsPanelAction = UnionOf<typeof detailsPanelActions>;
-export const loadDetailsPanel = (uuid: string) => detailsPanelActions.LOAD_DETAILS_PANEL(uuid);
+export const PROJECT_PROPERTIES_FORM_NAME = 'projectPropertiesFormName';
+export const PROJECT_PROPERTIES_DIALOG_NAME = 'projectPropertiesDialogName';
+export const loadDetailsPanel = (uuid: string) => detailsPanelActions.LOAD_DETAILS_PANEL(uuid);
+export const openProjectPropertiesDialog = () =>
+ (dispatch: Dispatch) => {
+ dispatch<any>(dialogActions.OPEN_DIALOG({ id: PROJECT_PROPERTIES_DIALOG_NAME, data: { } }));
+ };
+export const deleteProjectProperty = (key: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const { detailsPanel, resources } = getState();
+ const project = getResource(detailsPanel.resourceUuid)(resources) as ProjectResource;
+ try {
+ if (project) {
+ delete project.properties[key];
+ const updatedProject = await services.projectService.update(project.uuid, project);
+ dispatch(resourcesActions.SET_RESOURCES([updatedProject]));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully deleted.", hideDuration: 2000 }));
+ }
+ } catch (e) {
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_PROPERTIES_FORM_NAME }));
+ throw new Error('Could not remove property from the project.');
+ }
+ };
+export const createProjectProperty = (data: TagProperty) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const { detailsPanel, resources } = getState();
+ const project = getResource(detailsPanel.resourceUuid)(resources) as ProjectResource;
+ dispatch(startSubmit(PROJECT_PROPERTIES_FORM_NAME));
+ try {
+ if (project) {
+ project.properties[data.key] = data.value;
+ const updatedProject = await services.projectService.update(project.uuid, project);
+ dispatch(resourcesActions.SET_RESOURCES([updatedProject]));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Property has been successfully added.", hideDuration: 2000 }));
+ dispatch(stopSubmit(PROJECT_PROPERTIES_FORM_NAME));
+ }
+ return;
+ } catch (e) {
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_PROPERTIES_FORM_NAME }));
+ throw new Error('Could not add property to the project.');
+ }
+ };
+export const toggleDetailsPanel = () => (dispatch: Dispatch) => {
+ // because of material-ui issue resizing details panel breaks tabs.
+ // triggering window resize event fixes that.
+ setTimeout(() => {
+ window.dispatchEvent(new Event('resize'));
+ }, SLIDE_TIMEOUT);
+ dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+};
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
import { getProcess, ProcessStatus, getProcessStatus } from '~/store/processes/process';
import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
export const PROCESS_COPY_FORM_NAME = 'processCopyFormName';
const processStatus = getProcessStatus(process);
if (processStatus === ProcessStatus.DRAFT) {
dispatch<any>(resetPickerProjectTree());
+ dispatch<any>(initProjectsTreePicker(PROCESS_COPY_FORM_NAME));
const initialData: CopyFormDialogData = { name: `Copy of: ${resource.name}`, uuid: resource.uuid, ownerUuid: '' };
dispatch<any>(initialize(PROCESS_COPY_FORM_NAME, initialData));
dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_COPY_FORM_NAME, data: {} }));
import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
import { projectPanelActions } from '~/store/project-panel/project-panel-action';
import { getProcess, getProcessStatus, ProcessStatus } from '~/store/processes/process';
+import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
export const PROCESS_MOVE_FORM_NAME = 'processMoveFormName';
const processStatus = getProcessStatus(process);
if (processStatus === ProcessStatus.DRAFT) {
dispatch<any>(resetPickerProjectTree());
+ dispatch<any>(initProjectsTreePicker(PROCESS_MOVE_FORM_NAME));
dispatch(initialize(PROCESS_MOVE_FORM_NAME, resource));
dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_MOVE_FORM_NAME, data: {} }));
} else {
import { ServiceRepository } from '~/services/services';
import { updateResources } from '~/store/resources/resources-actions';
import { FilterBuilder } from '~/services/api/filter-builder';
-import { ContainerRequestResource } from '../../models/container-request';
+import { ContainerRequestResource } from '~/models/container-request';
import { Process } from './process';
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { projectPanelActions } from '~/store/project-panel/project-panel-action';
export const loadProcess = (containerRequestUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process> => {
dispatch<any>(updateResources(items));
return items;
};
+
+export const openRemoveProcessDialog = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(dialogActions.OPEN_DIALOG({
+ id: REMOVE_PROCESS_DIALOG,
+ data: {
+ title: 'Remove process permanently',
+ text: 'Are you sure you want to remove this process?',
+ confirmButtonLabel: 'Remove',
+ uuid
+ }
+ }));
+ };
+
+export const REMOVE_PROCESS_DIALOG = 'removeProcessDialog';
+
+export const removeProcessPermanently = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) =>{
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+ await services.containerRequestService.delete(uuid);
+ dispatch(projectPanelActions.REQUEST_ITEMS());
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000 }));
+ };
+
+
import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service";
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
+import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
export const PROJECT_MOVE_FORM_NAME = 'projectMoveFormName';
export const openMoveProjectDialog = (resource: { name: string, uuid: string }) =>
(dispatch: Dispatch) => {
dispatch<any>(resetPickerProjectTree());
+ dispatch<any>(initProjectsTreePicker(PROJECT_MOVE_FORM_NAME));
dispatch(initialize(PROJECT_MOVE_FORM_NAME, resource));
dispatch(dialogActions.OPEN_DIALOG({ id: PROJECT_MOVE_FORM_NAME, data: {} }));
};
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+
+export interface PickerIdProp {
+ pickerId: string;
+}
+
+export const pickerId =
+ (id: string) =>
+ <P extends PickerIdProp>(Component: React.ComponentType<P>) =>
+ (props: P) =>
+ <Component {...props} pickerId={id} />;
+
\ No newline at end of file
// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from "~/common/unionize";
-import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId } from '~/models/tree';
+import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId, Tree } from '~/models/tree';
import { Dispatch } from 'redux';
import { RootState } from '~/store/store';
import { ServiceRepository } from '~/services/services';
import { pipe, values } from 'lodash/fp';
import { ResourceKind } from '~/models/resource';
import { GroupContentsResource } from '~/services/groups-service/groups-service';
-import { CollectionDirectory, CollectionFile } from '~/models/collection-file';
import { getTreePicker, TreePicker } from './tree-picker';
import { ProjectsTreePickerItem } from '~/views-components/projects-tree-picker/generic-projects-tree-picker';
import { OrderBuilder } from '~/services/api/order-builder';
import { ProjectResource } from '~/models/project';
+import { mapTree } from '../../models/tree';
export const treePickerActions = unionize({
LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ id: string, nodes: Array<TreeNode<any>>, pickerId: string }>(),
+ APPEND_TREE_PICKER_NODE_SUBTREE: ofType<{ id: string, subtree: Tree<any>, pickerId: string }>(),
TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ id: string, pickerId: string }>(),
- ACTIVATE_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
+ ACTIVATE_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string, relatedTreePickers?: string[] }>(),
DEACTIVATE_TREE_PICKER_NODE: ofType<{ pickerId: string }>(),
TOGGLE_TREE_PICKER_NODE_SELECTION: ofType<{ id: string, pickerId: string }>(),
SELECT_TREE_PICKER_NODE: ofType<{ id: string | string[], pickerId: string }>(),
)();
export const getSelectedNodes = <Value>(pickerId: string) => (state: TreePicker) =>
getAllNodes<Value>(pickerId, node => node.selected)(state);
-
+
export const initProjectsTreePicker = (pickerId: string) =>
async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
const { home, shared, favorites } = getProjectsTreePickerIds(pickerId);
const node = getNode(id)(picker);
if (node && 'kind' in node.value && node.value.kind === ResourceKind.COLLECTION) {
- const files = await services.collectionService.files(node.value.portableDataHash);
- const data = getNodeDescendants('')(files).map(node => node.value);
-
- dispatch<any>(receiveTreePickerData<CollectionDirectory | CollectionFile>({
- id,
- pickerId,
- data,
- extractNodeData: value => ({
- id: value.id,
- status: TreeNodeStatus.LOADED,
- value,
- }),
- }));
+ const filesTree = await services.collectionService.files(node.value.portableDataHash);
+
+ dispatch(
+ treePickerActions.APPEND_TREE_PICKER_NODE_SUBTREE({
+ id,
+ pickerId,
+ subtree: mapTree(node => ({ ...node, status: TreeNodeStatus.LOADED }))(filesTree)
+ }));
+
+ dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
}
}
};
}
};
-
+export const SHARED_PROJECT_ID = 'Shared with me';
export const initSharedProject = (pickerId: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
dispatch(receiveTreePickerData({
id: '',
pickerId,
- data: [{ uuid: 'Shared with me', name: 'Shared with me' }],
+ data: [{ uuid: SHARED_PROJECT_ID, name: SHARED_PROJECT_ID }],
extractNodeData: value => ({
id: value.uuid,
status: TreeNodeStatus.INITIAL,
}));
};
+export const FAVORITES_PROJECT_ID = 'Favorites';
export const initFavoritesProject = (pickerId: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
dispatch(receiveTreePickerData({
id: '',
pickerId,
- data: [{ uuid: 'Favorites', name: 'Favorites' }],
+ data: [{ uuid: FAVORITES_PROJECT_ID, name: FAVORITES_PROJECT_ID }],
extractNodeData: value => ({
id: value.uuid,
status: TreeNodeStatus.INITIAL,
import { treePickerActions, TreePickerAction } from "./tree-picker-actions";
import { compose } from "redux";
import { activateNode, getNode, toggleNodeCollapse, toggleNodeSelection } from '~/models/tree';
+import { pipe } from 'lodash/fp';
+import { appendSubtree } from '~/models/tree';
export const treePickerReducer = (state: TreePicker = {}, action: TreePickerAction) =>
treePickerActions.match(action, {
LOAD_TREE_PICKER_NODE: ({ id, pickerId }) =>
updateOrCreatePicker(state, pickerId, setNodeStatus(id)(TreeNodeStatus.PENDING)),
+
LOAD_TREE_PICKER_NODE_SUCCESS: ({ id, nodes, pickerId }) =>
updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(id), setNodeStatus(id)(TreeNodeStatus.LOADED))),
+
+ APPEND_TREE_PICKER_NODE_SUBTREE: ({ id, subtree, pickerId}) =>
+ updateOrCreatePicker(state, pickerId, compose(appendSubtree(id, subtree), setNodeStatus(id)(TreeNodeStatus.LOADED))),
+
TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ id, pickerId }) =>
updateOrCreatePicker(state, pickerId, toggleNodeCollapse(id)),
- ACTIVATE_TREE_PICKER_NODE: ({ id, pickerId }) =>
- updateOrCreatePicker(state, pickerId, activateNode(id)),
+
+ ACTIVATE_TREE_PICKER_NODE: ({ id, pickerId, relatedTreePickers = [] }) =>
+ pipe(
+ () => relatedTreePickers.reduce(
+ (state, relatedPickerId) => updateOrCreatePicker(state, relatedPickerId, deactivateNode),
+ state
+ ),
+ state => updateOrCreatePicker(state, pickerId, activateNode(id))
+ )(),
+
DEACTIVATE_TREE_PICKER_NODE: ({ pickerId }) =>
updateOrCreatePicker(state, pickerId, deactivateNode),
+
TOGGLE_TREE_PICKER_NODE_SELECTION: ({ id, pickerId }) =>
updateOrCreatePicker(state, pickerId, toggleNodeSelection(id)),
+
SELECT_TREE_PICKER_NODE: ({ id, pickerId }) =>
updateOrCreatePicker(state, pickerId, selectNodes(id)),
+
DESELECT_TREE_PICKER_NODE: ({ id, pickerId }) =>
updateOrCreatePicker(state, pickerId, deselectNodes(id)),
+
RESET_TREE_PICKER: ({ pickerId }) =>
updateOrCreatePicker(state, pickerId, createTree),
+
EXPAND_TREE_PICKER_NODES: ({ pickerId, ids }) =>
updateOrCreatePicker(state, pickerId, expandNode(...ids)),
+
default: () => state
});
import { detailsPanelActions } from '~/store/details-panel/details-panel-action';
import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions';
import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
export const collectionActionSet: ContextMenuActionSet = [[
{
icon: DetailsIcon,
name: "View details",
execute: dispatch => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
}
},
// {
import { detailsPanelActions } from '~/store/details-panel/details-panel-action';
import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
export const collectionResourceActionSet: ContextMenuActionSet = [[
{
icon: DetailsIcon,
name: "View details",
execute: dispatch => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
}
},
{
import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
import { openProcessInputDialog } from "~/store/processes/process-input-actions";
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
export const processActionSet: ContextMenuActionSet = [[
{
icon: DetailsIcon,
name: "View details",
execute: dispatch => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
}
},
// {
import { openMoveProcessDialog } from '~/store/processes/process-move-actions';
import { openProcessUpdateDialog } from "~/store/processes/process-update-actions";
import { openCopyProcessDialog } from '~/store/processes/process-copy-actions';
-import { detailsPanelActions } from '~/store/details-panel/details-panel-action';
import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
+import { openRemoveProcessDialog } from "~/store/processes/processes-actions";
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
export const processResourceActionSet: ContextMenuActionSet = [[
{
icon: DetailsIcon,
name: "View details",
execute: dispatch => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
+ }
+ },
+ {
+ name: "Remove",
+ icon: RemoveIcon,
+ execute: (dispatch, resource) => {
+ dispatch<any>(openRemoveProcessDialog(resource.uuid));
}
}
- // {
- // icon: RemoveIcon,
- // name: "Remove",
- // execute: (dispatch, resource) => {
- // // add code
- // }
- // }
]];
import { ShareIcon } from '~/components/icon/icon';
import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
export const projectActionSet: ContextMenuActionSet = [[
{
icon: DetailsIcon,
name: "View details",
execute: dispatch => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
}
},
{
import { toggleCollectionTrashed } from "~/store/trash/trash-actions";
import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
export const trashedCollectionActionSet: ContextMenuActionSet = [[
{
icon: DetailsIcon,
name: "View details",
execute: dispatch => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
}
},
{
import * as classnames from "classnames";
import { connect } from 'react-redux';
import { RootState } from '~/store/store';
-import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
import { CloseIcon } from '~/components/icon/icon';
import { EmptyResource } from '~/models/empty';
import { Dispatch } from "redux";
import { getResource } from '~/store/resources/resources';
import { ResourceData } from "~/store/resources-data/resources-data-reducer";
import { getResourceData } from "~/store/resources-data/resources-data";
+import { toggleDetailsPanel, SLIDE_TIMEOUT } from '~/store/details-panel/details-panel-action';
type CssRules = 'root' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'tabContainer';
const DRAWER_WIDTH = 320;
-const SLIDE_TIMEOUT = 500;
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
background: theme.palette.background.paper,
const mapDispatchToProps = (dispatch: Dispatch) => ({
onCloseDrawer: () => {
- dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
+ dispatch<any>(toggleDetailsPanel());
}
});
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { ProjectIcon } from '~/components/icon/icon';
+import { compose } from 'redux';
+import { connect } from 'react-redux';
+import { openProjectPropertiesDialog } from '~/store/details-panel/details-panel-action';
+import { ProjectIcon, RenameIcon } from '~/components/icon/icon';
import { ProjectResource } from '~/models/project';
import { formatDate } from '~/common/formatters';
import { ResourceKind } from '~/models/resource';
import { DetailsData } from "./details-data";
import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
import { RichTextEditorLink } from '~/components/rich-text-editor-link/rich-text-editor-link';
+import { withStyles, StyleRulesCallback, Chip, WithStyles } from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
export class ProjectDetails extends DetailsData<ProjectResource> {
-
getIcon(className?: string) {
return <ProjectIcon className={className} />;
}
getDetails() {
- return <div>
- <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
- {/* Missing attr */}
- <DetailsAttribute label='Size' value='---' />
- <DetailsAttribute label='Owner' value={this.item.ownerUuid} lowercaseValue={true} />
- <DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
- <DetailsAttribute label='Created at' value={formatDate(this.item.createdAt)} />
- {/* Missing attr */}
- {/*<DetailsAttribute label='File size' value='1.4 GB' />*/}
- <DetailsAttribute label='Description'>
- {this.item.description ?
- <RichTextEditorLink
- title={`Description of ${this.item.name}`}
- content={this.item.description}
- label='Show full description' />
- : '---'
- }
- </DetailsAttribute>
- </div>;
+ return <ProjectDetailsComponent project={this.item} />;
+ }
+}
+
+type CssRules = 'tag' | 'editIcon';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ tag: {
+ marginRight: theme.spacing.unit,
+ marginBottom: theme.spacing.unit
+ },
+ editIcon: {
+ fontSize: '1.125rem',
+ cursor: 'pointer'
}
+});
+
+
+interface ProjectDetailsComponentDataProps {
+ project: ProjectResource;
+}
+
+interface ProjectDetailsComponentActionProps {
+ onClick: () => void;
}
+
+const mapDispatchToProps = ({ onClick: openProjectPropertiesDialog });
+
+type ProjectDetailsComponentProps = ProjectDetailsComponentDataProps & ProjectDetailsComponentActionProps & WithStyles<CssRules>;
+
+const ProjectDetailsComponent = connect(null, mapDispatchToProps)(
+ withStyles(styles)(
+ ({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
+ <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
+ {/* Missing attr */}
+ <DetailsAttribute label='Size' value='---' />
+ <DetailsAttribute label='Owner' value={project.ownerUuid} lowercaseValue={true} />
+ <DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
+ <DetailsAttribute label='Created at' value={formatDate(project.createdAt)} />
+ {/* Missing attr */}
+ {/*<DetailsAttribute label='File size' value='1.4 GB' />*/}
+ <DetailsAttribute label='Description'>
+ {project.description ?
+ <RichTextEditorLink
+ title={`Description of ${project.name}`}
+ content={project.description}
+ label='Show full description' />
+ : '---'
+ }
+ </DetailsAttribute>
+ <DetailsAttribute label='Properties'>
+ <div onClick={onClick}>
+ <RenameIcon className={classes.editIcon} />
+ </div>
+ </DetailsAttribute>
+ {
+ Object.keys(project.properties).map(k => {
+ return <Chip key={k} className={classes.tag} label={`${k}: ${project.properties[k]}`} />;
+ })
+ }
+ </div>
+));
\ No newline at end of file
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
+import { memoize } from "lodash/fp";
import { FormDialog } from '~/components/form-dialog/form-dialog';
import { CollectionNameField, CollectionDescriptionField, CollectionProjectPickerField } from '~/views-components/form-fields/collection-form-fields';
import { WithDialogProps } from '~/store/dialog/with-dialog';
import { InjectedFormProps } from 'redux-form';
import { CollectionPartialCopyFormData } from '~/store/collections/collection-partial-copy-actions';
+import { PickerIdProp } from "~/store/tree-picker/picker-id";
type DialogCollectionPartialCopyProps = WithDialogProps<string> & InjectedFormProps<CollectionPartialCopyFormData>;
-export const DialogCollectionPartialCopy = (props: DialogCollectionPartialCopyProps) =>
+export const DialogCollectionPartialCopy = (props: DialogCollectionPartialCopyProps & PickerIdProp) =>
<FormDialog
dialogTitle='Create a collection'
- formFields={CollectionPartialCopyFields}
+ formFields={CollectionPartialCopyFields(props.pickerId)}
submitLabel='Create a collection'
{...props}
/>;
-export const CollectionPartialCopyFields = () => <div>
- <CollectionNameField />
- <CollectionDescriptionField />
- <CollectionProjectPickerField />
-</div>;
+export const CollectionPartialCopyFields = memoize(
+ (pickerId: string) =>
+ () =>
+ <div>
+ <CollectionNameField />
+ <CollectionDescriptionField />
+ <CollectionProjectPickerField {...{ pickerId }} />
+ </div>);
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
+import { memoize } from 'lodash/fp';
import { InjectedFormProps, Field } from 'redux-form';
import { WithDialogProps } from '~/store/dialog/with-dialog';
import { FormDialog } from '~/components/form-dialog/form-dialog';
import { COPY_NAME_VALIDATION, COPY_FILE_VALIDATION } from '~/validators/validators';
import { TextField } from "~/components/text-field/text-field";
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+import { PickerIdProp } from '~/store/tree-picker/picker-id';
type CopyFormDialogProps = WithDialogProps<string> & InjectedFormProps<CopyFormDialogData>;
-export const DialogCopy = (props: CopyFormDialogProps) =>
+export const DialogCopy = (props: CopyFormDialogProps & PickerIdProp) =>
<FormDialog
dialogTitle='Make a copy'
- formFields={CopyDialogFields}
+ formFields={CopyDialogFields(props.pickerId)}
submitLabel='Copy'
{...props}
/>;
-const CopyDialogFields = () => <span>
- <Field
- name='name'
- component={TextField}
- validate={COPY_NAME_VALIDATION}
- label="Enter a new name for the copy" />
- <Field
- name="ownerUuid"
- component={ProjectTreePickerField}
- validate={COPY_FILE_VALIDATION} />
-</span>;
+const CopyDialogFields = memoize((pickerId: string) =>
+ () =>
+ <span>
+ <Field
+ name='name'
+ component={TextField}
+ validate={COPY_NAME_VALIDATION}
+ label="Enter a new name for the copy" />
+ <Field
+ name="ownerUuid"
+ component={ProjectTreePickerField}
+ validate={COPY_FILE_VALIDATION}
+ pickerId={pickerId}/>
+ </span>);
import { DialogCopy } from "~/views-components/dialog-copy/dialog-copy";
import { copyCollection } from '~/store/workbench/workbench-actions';
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+import { pickerId } from '~/store/tree-picker/picker-id';
export const CopyCollectionDialog = compose(
withDialog(COLLECTION_COPY_FORM_NAME),
onSubmit: (data, dispatch) => {
dispatch(copyCollection(data));
}
- })
+ }),
+ pickerId(COLLECTION_COPY_FORM_NAME),
)(DialogCopy);
\ No newline at end of file
import { DialogCopy } from "~/views-components/dialog-copy/dialog-copy";
import { copyProcess } from '~/store/workbench/workbench-actions';
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+import { pickerId } from "~/store/tree-picker/picker-id";
export const CopyProcessDialog = compose(
withDialog(PROCESS_COPY_FORM_NAME),
onSubmit: (data, dispatch) => {
dispatch(copyProcess(data));
}
- })
+ }),
+ pickerId(PROCESS_COPY_FORM_NAME),
)(DialogCopy);
\ No newline at end of file
import { COLLECTION_MOVE_FORM_NAME } from '~/store/collections/collection-move-actions';
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { moveCollection } from '~/store/workbench/workbench-actions';
+import { pickerId } from '~/store/tree-picker/picker-id';
export const MoveCollectionDialog = compose(
withDialog(COLLECTION_MOVE_FORM_NAME),
onSubmit: (data, dispatch) => {
dispatch(moveCollection(data));
}
- })
+ }),
+ pickerId(COLLECTION_MOVE_FORM_NAME),
)(DialogMoveTo);
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { DialogMoveTo } from '~/views-components/dialog-move/dialog-move-to';
import { moveProcess } from '~/store/workbench/workbench-actions';
+import { pickerId } from '~/store/tree-picker/picker-id';
export const MoveProcessDialog = compose(
withDialog(PROCESS_MOVE_FORM_NAME),
onSubmit: (data, dispatch) => {
dispatch(moveProcess(data));
}
- })
+ }),
+ pickerId(PROCESS_MOVE_FORM_NAME),
)(DialogMoveTo);
\ No newline at end of file
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { DialogMoveTo } from '~/views-components/dialog-move/dialog-move-to';
import { moveProject } from '~/store/workbench/workbench-actions';
+import { pickerId } from '~/store/tree-picker/picker-id';
export const MoveProjectDialog = compose(
withDialog(PROJECT_MOVE_FORM_NAME),
onSubmit: (data, dispatch) => {
dispatch(moveProject(data));
}
- })
+ }),
+ pickerId(PROJECT_MOVE_FORM_NAME),
)(DialogMoveTo);
import { withDialog, } from '~/store/dialog/with-dialog';
import { CollectionPartialCopyFormData, copyCollectionPartial, COLLECTION_PARTIAL_COPY_FORM_NAME } from '~/store/collections/collection-partial-copy-actions';
import { DialogCollectionPartialCopy } from "~/views-components/dialog-copy/dialog-collection-partial-copy";
+import { pickerId } from "~/store/tree-picker/picker-id";
export const PartialCopyCollectionDialog = compose(
onSubmit: (data, dispatch) => {
dispatch(copyCollectionPartial(data));
}
- }))(DialogCollectionPartialCopy);
\ No newline at end of file
+ }),
+ pickerId(COLLECTION_PARTIAL_COPY_FORM_NAME),
+)(DialogCollectionPartialCopy);
\ No newline at end of file
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
+import { memoize } from 'lodash/fp';
import { InjectedFormProps, Field } from 'redux-form';
import { WithDialogProps } from '~/store/dialog/with-dialog';
import { FormDialog } from '~/components/form-dialog/form-dialog';
import { ProjectTreePickerField } from '~/views-components/project-tree-picker/project-tree-picker';
import { MOVE_TO_VALIDATION } from '~/validators/validators';
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
+import { PickerIdProp } from "~/store/tree-picker/picker-id";
-export const DialogMoveTo = (props: WithDialogProps<string> & InjectedFormProps<MoveToFormDialogData>) =>
+export const DialogMoveTo = (props: WithDialogProps<string> & InjectedFormProps<MoveToFormDialogData> & PickerIdProp) =>
<FormDialog
dialogTitle='Move to'
- formFields={MoveToDialogFields}
+ formFields={MoveToDialogFields(props.pickerId)}
submitLabel='Move'
{...props}
/>;
-const MoveToDialogFields = () =>
- <Field
- name="ownerUuid"
- component={ProjectTreePickerField}
- validate={MOVE_TO_VALIDATION} />;
+const MoveToDialogFields = memoize(
+ (pickerId: string) => () =>
+ <Field
+ name="ownerUuid"
+ pickerId={pickerId}
+ component={ProjectTreePickerField}
+ validate={MOVE_TO_VALIDATION} />);
import { Field, WrappedFieldProps } from "redux-form";
import { TextField } from "~/components/text-field/text-field";
import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/validators";
-import { ProjectTreePicker } from "~/views-components/project-tree-picker/project-tree-picker";
+import { ProjectTreePicker, ProjectTreePickerField } from "~/views-components/project-tree-picker/project-tree-picker";
+import { PickerIdProp } from '../../store/tree-picker/picker-id';
export const CollectionNameField = () =>
<Field
validate={COLLECTION_DESCRIPTION_VALIDATION}
label="Description - optional" />;
-export const CollectionProjectPickerField = () =>
+export const CollectionProjectPickerField = (props: PickerIdProp) =>
<Field
name="projectUuid"
- component={ProjectPicker}
+ pickerId={props.pickerId}
+ component={ProjectTreePickerField}
validate={COLLECTION_PROJECT_VALIDATION} />;
-
-const ProjectPicker = (props: WrappedFieldProps) =>
- <div style={{ height: '144px', display: 'flex', flexDirection: 'column' }}>
- <ProjectTreePicker onChange={projectUuid => props.input.onChange(projectUuid)} />
- </div>;
import { connect } from 'react-redux';
import { RootState } from '~/store/store';
import { matchWorkflowRoute } from '~/routes/routes';
+import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
interface MainContentBarProps {
onDetailsPanelToggle: () => void;
export const MainContentBar = connect((state: RootState) => ({
buttonVisible: !isWorkflowPath(state)
}), {
- onDetailsPanelToggle: detailsPanelActions.TOGGLE_DETAILS_PANEL
+ onDetailsPanelToggle: toggleDetailsPanel
})((props: MainContentBarProps) =>
<Toolbar>
<Grid container>
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch, compose } from 'redux';
+import { connect } from "react-redux";
+import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
+import { withDialog, WithDialogProps } from "~/store/dialog/with-dialog";
+import { removeProcessPermanently, REMOVE_PROCESS_DIALOG } from '~/store/processes/processes-actions';
+
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+ onConfirm: () => {
+ props.closeDialog();
+ dispatch<any>(removeProcessPermanently(props.data.uuid));
+ }
+});
+
+export const RemoveProcessDialog = compose(
+ withDialog(REMOVE_PROCESS_DIALOG),
+ connect(null, mapDispatchToProps)
+)(ConfirmationDialog);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dispatch } from "redux";
+import { connect } from "react-redux";
+import { RootState } from '~/store/store';
+import { withDialog, WithDialogProps } from "~/store/dialog/with-dialog";
+import { ProjectResource } from '~/models/project';
+import { PROJECT_PROPERTIES_DIALOG_NAME, deleteProjectProperty } from '~/store/details-panel/details-panel-action';
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Chip, withStyles, StyleRulesCallback, WithStyles } from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { ProjectPropertiesForm } from '~/views-components/project-properties-dialog/project-properties-form';
+import { getResource } from '~/store/resources/resources';
+
+type CssRules = 'tag';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ tag: {
+ marginRight: theme.spacing.unit,
+ marginBottom: theme.spacing.unit
+ }
+});
+
+interface ProjectPropertiesDialogDataProps {
+ project: ProjectResource;
+}
+
+interface ProjectPropertiesDialogActionProps {
+ handleDelete: (key: string) => void;
+}
+
+const mapStateToProps = ({ detailsPanel, resources }: RootState): ProjectPropertiesDialogDataProps => {
+ const project = getResource(detailsPanel.resourceUuid)(resources) as ProjectResource;
+ return { project };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): ProjectPropertiesDialogActionProps => ({
+ handleDelete: (key: string) => dispatch<any>(deleteProjectProperty(key))
+});
+
+type ProjectPropertiesDialogProps = ProjectPropertiesDialogDataProps & ProjectPropertiesDialogActionProps & WithDialogProps<{}> & WithStyles<CssRules>;
+
+export const ProjectPropertiesDialog = connect(mapStateToProps, mapDispatchToProps)(
+ withStyles(styles)(
+ withDialog(PROJECT_PROPERTIES_DIALOG_NAME)(
+ ({ classes, open, closeDialog, handleDelete, project }: ProjectPropertiesDialogProps) =>
+ <Dialog open={open}
+ onClose={closeDialog}
+ fullWidth
+ maxWidth='sm'>
+ <DialogTitle>Properties</DialogTitle>
+ <DialogContent>
+ <ProjectPropertiesForm />
+ {project && project.properties &&
+ Object.keys(project.properties).map(k => {
+ return <Chip key={k} className={classes.tag}
+ onDelete={() => handleDelete(k)}
+ label={`${k}: ${project.properties[k]}`} />;
+ })
+ }
+ </DialogContent>
+ <DialogActions>
+ <Button
+ variant='flat'
+ color='primary'
+ onClick={closeDialog}>
+ Close
+ </Button>
+ </DialogActions>
+ </Dialog>
+)));
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { reduxForm, Field, reset } from 'redux-form';
+import { compose, Dispatch } from 'redux';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress } from '@material-ui/core';
+import { TagProperty } from '~/models/tag';
+import { TextField } from '~/components/text-field/text-field';
+import { TAG_VALUE_VALIDATION, TAG_KEY_VALIDATION } from '~/validators/validators';
+import { PROJECT_PROPERTIES_FORM_NAME, createProjectProperty } from '~/store/details-panel/details-panel-action';
+
+type CssRules = 'root' | 'keyField' | 'valueField' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ display: 'flex'
+ },
+ keyField: {
+ width: '40%',
+ marginRight: theme.spacing.unit * 3
+ },
+ valueField: {
+ width: '40%',
+ marginRight: theme.spacing.unit * 3
+ },
+ buttonWrapper: {
+ paddingTop: '14px',
+ position: 'relative',
+ },
+ saveButton: {
+ boxShadow: 'none'
+ },
+ circularProgress: {
+ position: 'absolute',
+ top: -9,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ margin: 'auto'
+ }
+});
+
+interface ProjectPropertiesFormDataProps {
+ submitting: boolean;
+ invalid: boolean;
+ pristine: boolean;
+}
+
+interface ProjectPropertiesFormActionProps {
+ handleSubmit: any;
+}
+
+type ProjectPropertiesFormProps = ProjectPropertiesFormDataProps & ProjectPropertiesFormActionProps & WithStyles<CssRules>;
+
+export const ProjectPropertiesForm = compose(
+ reduxForm({
+ form: PROJECT_PROPERTIES_FORM_NAME,
+ onSubmit: (data: TagProperty, dispatch: Dispatch) => {
+ dispatch<any>(createProjectProperty(data));
+ dispatch(reset(PROJECT_PROPERTIES_FORM_NAME));
+ }
+ }),
+ withStyles(styles))(
+ ({ classes, submitting, pristine, invalid, handleSubmit }: ProjectPropertiesFormProps) =>
+ <form onSubmit={handleSubmit} className={classes.root}>
+ <div className={classes.keyField}>
+ <Field name="key"
+ disabled={submitting}
+ component={TextField}
+ validate={TAG_KEY_VALIDATION}
+ label="Key" />
+ </div>
+ <div className={classes.valueField}>
+ <Field name="value"
+ disabled={submitting}
+ component={TextField}
+ validate={TAG_VALUE_VALIDATION}
+ label="Value" />
+ </div>
+ <div className={classes.buttonWrapper}>
+ <Button type="submit" className={classes.saveButton}
+ color="primary"
+ size='small'
+ disabled={invalid || submitting || pristine}
+ variant="contained">
+ ADD
+ </Button>
+ {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
+ </div>
+ </form>
+ );
import { ServiceRepository } from "~/services/services";
import { WrappedFieldProps } from 'redux-form';
import { TreePickerId } from '~/models/tree';
+import { ProjectsTreePicker } from '~/views-components/projects-tree-picker/projects-tree-picker';
+import { ProjectsTreePickerItem } from '~/views-components/projects-tree-picker/generic-projects-tree-picker';
+import { PickerIdProp } from '~/store/tree-picker/picker-id';
type ProjectTreePickerProps = Pick<TreePickerProps<ProjectResource>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
isActive={item.active}
hasMargin={true} />;
-export const ProjectTreePickerField = (props: WrappedFieldProps) =>
+export const ProjectTreePickerField = (props: WrappedFieldProps & PickerIdProp) =>
<div style={{ height: '200px', display: 'flex', flexDirection: 'column' }}>
- <ProjectTreePicker onChange={handleChange(props)} />
+ <ProjectsTreePicker
+ pickerId={props.pickerId}
+ toggleItemActive={handleChange(props)} />
{props.meta.dirty && props.meta.error &&
<Typography variant='caption' color='error'>
{props.meta.error}
</Typography>}
</div>;
-const handleChange = (props: WrappedFieldProps) => (value: string) =>
- props.input.value === value
- ? props.input.onChange('')
- : props.input.onChange(value);
-
+const handleChange = (props: WrappedFieldProps) =>
+ (_: any, { id }: TreeItem<ProjectsTreePickerItem>) =>
+ props.input.onChange(id);
import * as React from "react";
import { Dispatch } from "redux";
import { connect } from "react-redux";
+import { isEqual } from 'lodash/fp';
import { TreeItem, TreeItemStatus } from '~/components/tree/tree';
import { ProjectResource } from "~/models/project";
import { treePickerActions } from "~/store/tree-picker/tree-picker-actions";
includeFiles?: boolean;
rootItemIcon: IconType;
showSelection?: boolean;
+ relatedTreePickers?: string[];
+ disableActivation?: string[];
loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string, includeCollections?: boolean, inlcudeFiles?: boolean) => void;
}
showSelection: isSelectionVisible(showSelection),
});
-const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeFiles, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({
+const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeFiles, relatedTreePickers, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({
onContextMenu: () => { return; },
toggleItemActive: (event, item, pickerId) => {
- dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: item.id, pickerId }));
+
+ const { disableActivation = [] } = props;
+ if(disableActivation.some(isEqual(item.id))){
+ return;
+ }
+
+ dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: item.id, pickerId, relatedTreePickers }));
if (props.toggleItemActive) {
props.toggleItemActive(event, item, pickerId);
}
},
toggleItemSelection: (event, item, pickerId) => {
dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id: item.id, pickerId }));
- if(props.toggleItemSelection){
+ if (props.toggleItemSelection) {
props.toggleItemSelection(event, item, pickerId);
}
},
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
+import { values, memoize, pipe, pick } from 'lodash/fp';
import { HomeTreePicker } from '~/views-components/projects-tree-picker/home-tree-picker';
import { SharedTreePicker } from '~/views-components/projects-tree-picker/shared-tree-picker';
import { FavoritesTreePicker } from '~/views-components/projects-tree-picker/favorites-tree-picker';
-import { getProjectsTreePickerIds } from '~/store/tree-picker/tree-picker-actions';
+import { getProjectsTreePickerIds, SHARED_PROJECT_ID, FAVORITES_PROJECT_ID } from '~/store/tree-picker/tree-picker-actions';
import { TreeItem } from '~/components/tree/tree';
import { ProjectsTreePickerItem } from './generic-projects-tree-picker';
export const ProjectsTreePicker = ({ pickerId, ...props }: ProjectsTreePickerProps) => {
const { home, shared, favorites } = getProjectsTreePickerIds(pickerId);
+ const relatedTreePickers = getRelatedTreePickers(pickerId);
+ const p = {
+ ...props,
+ relatedTreePickers,
+ disableActivation
+ };
return <div>
- <HomeTreePicker pickerId={home} {...props} />
- <SharedTreePicker pickerId={shared} {...props} />
- <FavoritesTreePicker pickerId={favorites} {...props} />
+ <HomeTreePicker pickerId={home} {...p} />
+ <SharedTreePicker pickerId={shared} {...p} />
+ <FavoritesTreePicker pickerId={favorites} {...p} />
</div>;
};
+
+const getRelatedTreePickers = memoize(pipe(getProjectsTreePickerIds, values));
+const disableActivation = [SHARED_PROJECT_ID, FAVORITES_PROJECT_ID];
import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog';
import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
+import { RemoveProcessDialog } from '~/views-components/process-remove-dialog/process-remove-dialog';
import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
import { Grid } from '@material-ui/core';
import { TrashPanel } from "~/views/trash-panel/trash-panel";
import { SharingDialog } from '~/views-components/sharing-dialog/sharing-dialog';
import { AdvancedTabDialog } from '~/views-components/advanced-tab-dialog/advanced-tab-dialog';
import { ProcessInputDialog } from '~/views-components/process-input-dialog/process-input-dialog';
+import { ProjectPropertiesDialog } from '~/views-components/project-properties-dialog/project-properties-dialog';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
<PartialCopyCollectionDialog />
<ProcessCommandDialog />
<ProcessInputDialog />
+ <ProjectPropertiesDialog />
+ <RemoveProcessDialog />
<RenameFileDialog />
<RichTextEditorDialog />
<SharingDialog />