From: Lucas Di Pentima Date: Fri, 17 Dec 2021 21:03:14 +0000 (-0300) Subject: 18219: Adds property edition capabilities to create & update dialogs. X-Git-Tag: 2.4.0~21^2~9 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/40b55e355fe0a206a4cb3eec676e44d935b7d5ec 18219: Adds property edition capabilities to create & update dialogs. There's too much code duplication. Some might be simple to avoid. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/src/store/collections/collection-create-actions.ts b/src/store/collections/collection-create-actions.ts index 81d8948c..22202b15 100644 --- a/src/store/collections/collection-create-actions.ts +++ b/src/store/collections/collection-create-actions.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import { Dispatch } from "redux"; -import { reset, startSubmit, stopSubmit, initialize, FormErrors } from 'redux-form'; +import { reset, startSubmit, stopSubmit, initialize, FormErrors, change, formValueSelector } from 'redux-form'; import { RootState } from 'store/store'; import { getUserUuid } from "common/getuser"; import { dialogActions } from "store/dialog/dialog-actions"; @@ -15,15 +15,24 @@ import { progressIndicatorActions } from "store/progress-indicator/progress-indi import { isProjectOrRunProcessRoute } from 'store/projects/project-create-actions'; import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; import { CollectionResource } from "models/collection"; +import { ResourcePropertiesFormData } from "views-components/resource-properties-form/resource-properties-form"; +import { addProperty, deleteProperty } from "lib/resource-properties"; export interface CollectionCreateFormDialogData { ownerUuid: string; name: string; description: string; storageClassesDesired: string[]; + properties: CollectionProperties; +} + +export interface CollectionProperties { + [key: string]: string | string[]; } export const COLLECTION_CREATE_FORM_NAME = "collectionCreateFormName"; +export const COLLECTION_CREATE_PROPERTIES_FORM_NAME = "collectionCreatePropertiesFormName"; +export const COLLECTION_CREATE_FORM_SELECTOR = formValueSelector(COLLECTION_CREATE_FORM_NAME); export const openCollectionCreateDialog = (ownerUuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { @@ -69,3 +78,23 @@ export const createCollection = (data: CollectionCreateFormDialogData) => dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_CREATE_FORM_NAME)); } }; + +export const addPropertyToCreateCollectionForm = (data: ResourcePropertiesFormData) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const properties = { ...COLLECTION_CREATE_FORM_SELECTOR(getState(), 'properties') }; + const key = data.keyID || data.key; + const value = data.valueID || data.value; + dispatch(change( + COLLECTION_CREATE_FORM_NAME, + 'properties', + addProperty(properties, key, value))); + }; + +export const removePropertyFromCreateCollectionForm = (key: string, value: string) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const properties = { ...COLLECTION_CREATE_FORM_SELECTOR(getState(), 'properties') }; + dispatch(change( + COLLECTION_CREATE_FORM_NAME, + 'properties', + deleteProperty(properties, key, value))); + }; diff --git a/src/store/collections/collection-update-actions.ts b/src/store/collections/collection-update-actions.ts index 04f42b8d..0096bc48 100644 --- a/src/store/collections/collection-update-actions.ts +++ b/src/store/collections/collection-update-actions.ts @@ -3,7 +3,14 @@ // SPDX-License-Identifier: AGPL-3.0 import { Dispatch } from "redux"; -import { FormErrors, initialize, startSubmit, stopSubmit } from 'redux-form'; +import { + change, + FormErrors, + formValueSelector, + initialize, + startSubmit, + stopSubmit +} from 'redux-form'; import { RootState } from "store/store"; import { collectionPanelActions } from "store/collection-panel/collection-panel-action"; import { dialogActions } from "store/dialog/dialog-actions"; @@ -15,15 +22,21 @@ import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions"; import { updateResources } from "../resources/resources-actions"; import { loadDetailsPanel } from "../details-panel/details-panel-action"; import { getResource } from "store/resources/resources"; +import { CollectionProperties } from "./collection-create-actions"; +import { ResourcePropertiesFormData } from "views-components/resource-properties-form/resource-properties-form"; +import { addProperty, deleteProperty } from "lib/resource-properties"; export interface CollectionUpdateFormDialogData { uuid: string; name: string; description?: string; storageClassesDesired?: string[]; + properties?: CollectionProperties; } export const COLLECTION_UPDATE_FORM_NAME = 'collectionUpdateFormName'; +export const COLLECTION_UPDATE_PROPERTIES_FORM_NAME = "collectionCreatePropertiesFormName"; +export const COLLECTION_UPDATE_FORM_SELECTOR = formValueSelector(COLLECTION_UPDATE_FORM_NAME); export const openCollectionUpdateDialog = (resource: CollectionUpdateFormDialogData) => (dispatch: Dispatch) => { @@ -41,7 +54,8 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) => services.collectionService.update(uuid, { name: collection.name, storageClassesDesired: collection.storageClassesDesired, - description: collection.description } + description: collection.description, + properties: collection.properties } ).then(updatedCollection => { updatedCollection = {...cachedCollection, ...updatedCollection}; dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: updatedCollection as CollectionResource })); @@ -69,3 +83,23 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) => } ); }; + +export const addPropertyToUpdateCollectionForm = (data: ResourcePropertiesFormData) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const properties = { ...COLLECTION_UPDATE_FORM_SELECTOR(getState(), 'properties') }; + const key = data.keyID || data.key; + const value = data.valueID || data.value; + dispatch(change( + COLLECTION_UPDATE_FORM_NAME, + 'properties', + addProperty(properties, key, value))); + }; + +export const removePropertyFromUpdateCollectionForm = (key: string, value: string) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const properties = { ...COLLECTION_UPDATE_FORM_SELECTOR(getState(), 'properties') }; + dispatch(change( + COLLECTION_UPDATE_FORM_NAME, + 'properties', + deleteProperty(properties, key, value))); + }; diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 9a8733ba..38433eb2 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -41,6 +41,7 @@ export type ContextMenuResource = { outputUuid?: string; workflowUuid?: string; storageClassesDesired?: string[]; + properties?: { [key: string]: string | string[] }; }; export const isKeyboardClick = (event: React.MouseEvent) => event.nativeEvent.detail === 0; diff --git a/src/store/projects/project-update-actions.ts b/src/store/projects/project-update-actions.ts index ba176753..e5fc34d8 100644 --- a/src/store/projects/project-update-actions.ts +++ b/src/store/projects/project-update-actions.ts @@ -3,23 +3,40 @@ // SPDX-License-Identifier: AGPL-3.0 import { Dispatch } from "redux"; -import { FormErrors, initialize, reset, startSubmit, stopSubmit } from 'redux-form'; +import { + change, + FormErrors, + formValueSelector, + initialize, + reset, + startSubmit, + stopSubmit +} from 'redux-form'; import { RootState } from "store/store"; import { dialogActions } from "store/dialog/dialog-actions"; -import { getCommonResourceServiceError, CommonResourceServiceError } from "services/common-service/common-resource-service"; +import { + getCommonResourceServiceError, + CommonResourceServiceError +} from "services/common-service/common-resource-service"; import { ServiceRepository } from "services/services"; import { projectPanelActions } from 'store/project-panel/project-panel-action'; import { GroupClass } from "models/group"; import { Participant } from "views-components/sharing-dialog/participant-select"; +import { ResourcePropertiesFormData } from "views-components/resource-properties-form/resource-properties-form"; +import { addProperty, deleteProperty } from "lib/resource-properties"; +import { ProjectProperties } from "./project-create-actions"; export interface ProjectUpdateFormDialogData { uuid: string; name: string; users?: Participant[]; description?: string; + properties?: ProjectProperties; } export const PROJECT_UPDATE_FORM_NAME = 'projectUpdateFormName'; +export const PROJECT_UPDATE_PROPERTIES_FORM_NAME = 'projectUpdatePropertiesFormName'; +export const PROJECT_UPDATE_FORM_SELECTOR = formValueSelector(PROJECT_UPDATE_FORM_NAME); export const openProjectUpdateDialog = (resource: ProjectUpdateFormDialogData) => (dispatch: Dispatch, getState: () => RootState) => { @@ -32,7 +49,13 @@ export const updateProject = (project: ProjectUpdateFormDialogData) => const uuid = project.uuid || ''; dispatch(startSubmit(PROJECT_UPDATE_FORM_NAME)); try { - const updatedProject = await services.projectService.update(uuid, { name: project.name, description: project.description }); + const updatedProject = await services.projectService.update( + uuid, + { + name: project.name, + description: project.description, + properties: project.properties, + }); dispatch(projectPanelActions.REQUEST_ITEMS()); dispatch(reset(PROJECT_UPDATE_FORM_NAME)); dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_UPDATE_FORM_NAME })); @@ -45,3 +68,23 @@ export const updateProject = (project: ProjectUpdateFormDialogData) => return ; } }; + +export const addPropertyToUpdateProjectForm = (data: ResourcePropertiesFormData) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const properties = { ...PROJECT_UPDATE_FORM_SELECTOR(getState(), 'properties') }; + const key = data.keyID || data.key; + const value = data.valueID || data.value; + dispatch(change( + PROJECT_UPDATE_FORM_NAME, + 'properties', + addProperty(properties, key, value))); + }; + +export const removePropertyFromUpdateProjectForm = (key: string, value: string) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const properties = { ...PROJECT_UPDATE_FORM_SELECTOR(getState(), 'properties') }; + dispatch(change( + PROJECT_UPDATE_FORM_NAME, + 'properties', + deleteProperty(properties, key, value))); + }; diff --git a/src/views-components/collection-properties/create-collection-properties-form.tsx b/src/views-components/collection-properties/create-collection-properties-form.tsx new file mode 100644 index 00000000..8e3f8eb8 --- /dev/null +++ b/src/views-components/collection-properties/create-collection-properties-form.tsx @@ -0,0 +1,32 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { reduxForm, reset } from 'redux-form'; +import { withStyles } from '@material-ui/core'; +import { + COLLECTION_CREATE_PROPERTIES_FORM_NAME, + addPropertyToCreateCollectionForm +} from 'store/collections/collection-create-actions'; +import { + ResourcePropertiesForm, + ResourcePropertiesFormData +} from 'views-components/resource-properties-form/resource-properties-form'; + +const Form = withStyles( + ({ spacing }) => ( + { container: + { + paddingTop: spacing.unit, + margin: 0, + } + }) + )(ResourcePropertiesForm); + +export const CreateCollectionPropertiesForm = reduxForm({ + form: COLLECTION_CREATE_PROPERTIES_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(addPropertyToCreateCollectionForm(data)); + dispatch(reset(COLLECTION_CREATE_PROPERTIES_FORM_NAME)); + } +})(Form); \ No newline at end of file diff --git a/src/views-components/collection-properties/create-collection-properties-list.tsx b/src/views-components/collection-properties/create-collection-properties-list.tsx new file mode 100644 index 00000000..9784b55e --- /dev/null +++ b/src/views-components/collection-properties/create-collection-properties-list.tsx @@ -0,0 +1,70 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + withStyles, + StyleRulesCallback, + WithStyles, +} from '@material-ui/core'; +import { RootState } from 'store/store'; +import { + removePropertyFromCreateCollectionForm, + COLLECTION_CREATE_FORM_SELECTOR, + CollectionProperties +} from 'store/collections/collection-create-actions'; +import { ArvadosTheme } from 'common/custom-theme'; +import { getPropertyChip } from '../resource-properties-form/property-chip'; + +type CssRules = 'tag'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + tag: { + marginRight: theme.spacing.unit, + marginBottom: theme.spacing.unit + } +}); + +interface CreateCollectionPropertiesListDataProps { + properties: CollectionProperties; +} + +interface CreateCollectionPropertiesListActionProps { + handleDelete: (key: string, value: string) => void; +} + +const mapStateToProps = (state: RootState): CreateCollectionPropertiesListDataProps => { + const properties = COLLECTION_CREATE_FORM_SELECTOR(state, 'properties'); + return { properties }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): CreateCollectionPropertiesListActionProps => ({ + handleDelete: (key: string, value: string) => dispatch(removePropertyFromCreateCollectionForm(key, value)) +}); + +type CreateCollectionPropertiesListProps = CreateCollectionPropertiesListDataProps & + CreateCollectionPropertiesListActionProps & WithStyles; + +const List = withStyles(styles)( + ({ classes, handleDelete, properties }: CreateCollectionPropertiesListProps) => +
+ {properties && + Object.keys(properties).map(k => + Array.isArray(properties[k]) + ? (properties[k] as string[]).map((v: string) => + getPropertyChip( + k, v, + () => handleDelete(k, v), + classes.tag)) + : getPropertyChip( + k, (properties[k] as string), + () => handleDelete(k, (properties[k] as string)), + classes.tag)) + } +
+); + +export const CreateCollectionPropertiesList = connect(mapStateToProps, mapDispatchToProps)(List); \ No newline at end of file diff --git a/src/views-components/collection-properties/update-collection-properties-form.tsx b/src/views-components/collection-properties/update-collection-properties-form.tsx new file mode 100644 index 00000000..dc00de30 --- /dev/null +++ b/src/views-components/collection-properties/update-collection-properties-form.tsx @@ -0,0 +1,32 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { reduxForm, reset } from 'redux-form'; +import { withStyles } from '@material-ui/core'; +import { + addPropertyToUpdateCollectionForm, + COLLECTION_UPDATE_PROPERTIES_FORM_NAME +} from 'store/collections/collection-update-actions'; +import { + ResourcePropertiesForm, + ResourcePropertiesFormData +} from 'views-components/resource-properties-form/resource-properties-form'; + +const Form = withStyles( + ({ spacing }) => ( + { container: + { + paddingTop: spacing.unit, + margin: 0, + } + }) + )(ResourcePropertiesForm); + +export const UpdateCollectionPropertiesForm = reduxForm({ + form: COLLECTION_UPDATE_PROPERTIES_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(addPropertyToUpdateCollectionForm(data)); + dispatch(reset(COLLECTION_UPDATE_PROPERTIES_FORM_NAME)); + } +})(Form); \ No newline at end of file diff --git a/src/views-components/collection-properties/update-collection-properties-list.tsx b/src/views-components/collection-properties/update-collection-properties-list.tsx new file mode 100644 index 00000000..26cf5e70 --- /dev/null +++ b/src/views-components/collection-properties/update-collection-properties-list.tsx @@ -0,0 +1,70 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + withStyles, + StyleRulesCallback, + WithStyles, +} from '@material-ui/core'; +import { RootState } from 'store/store'; +import { + removePropertyFromUpdateCollectionForm, + COLLECTION_UPDATE_FORM_SELECTOR, +} from 'store/collections/collection-update-actions'; +import { ArvadosTheme } from 'common/custom-theme'; +import { getPropertyChip } from '../resource-properties-form/property-chip'; +import { CollectionProperties } from 'store/collections/collection-create-actions'; + +type CssRules = 'tag'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + tag: { + marginRight: theme.spacing.unit, + marginBottom: theme.spacing.unit + } +}); + +interface UpdateCollectionPropertiesListDataProps { + properties: CollectionProperties; +} + +interface UpdateCollectionPropertiesListActionProps { + handleDelete: (key: string, value: string) => void; +} + +const mapStateToProps = (state: RootState): UpdateCollectionPropertiesListDataProps => { + const properties = COLLECTION_UPDATE_FORM_SELECTOR(state, 'properties'); + return { properties }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): UpdateCollectionPropertiesListActionProps => ({ + handleDelete: (key: string, value: string) => dispatch(removePropertyFromUpdateCollectionForm(key, value)) +}); + +type UpdateCollectionPropertiesListProps = UpdateCollectionPropertiesListDataProps & + UpdateCollectionPropertiesListActionProps & WithStyles; + +const List = withStyles(styles)( + ({ classes, handleDelete, properties }: UpdateCollectionPropertiesListProps) => +
+ {properties && + Object.keys(properties).map(k => + Array.isArray(properties[k]) + ? (properties[k] as string[]).map((v: string) => + getPropertyChip( + k, v, + () => handleDelete(k, v), + classes.tag)) + : getPropertyChip( + k, (properties[k] as string), + () => handleDelete(k, (properties[k] as string)), + classes.tag)) + } +
+); + +export const UpdateCollectionPropertiesList = connect(mapStateToProps, mapDispatchToProps)(List); \ No newline at end of file diff --git a/src/views-components/dialog-create/dialog-collection-create.tsx b/src/views-components/dialog-create/dialog-collection-create.tsx index c85a6d12..b75ad50f 100644 --- a/src/views-components/dialog-create/dialog-collection-create.tsx +++ b/src/views-components/dialog-create/dialog-collection-create.tsx @@ -14,6 +14,9 @@ import { } from 'views-components/form-fields/collection-form-fields'; import { FileUploaderField } from '../file-uploader/file-uploader'; import { ResourceParentField } from '../form-fields/resource-form-fields'; +import { CreateCollectionPropertiesList } from 'views-components/collection-properties/create-collection-properties-list'; +import { CreateCollectionPropertiesForm } from 'views-components/collection-properties/create-collection-properties-form'; +import { FormGroup, FormLabel } from '@material-ui/core'; type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps; @@ -29,6 +32,11 @@ const CollectionAddFields = () => + Properties + + + + & InjectedFormProps; @@ -26,6 +27,9 @@ const ProjectAddFields = () => - - + Properties + + + + ; diff --git a/src/views-components/dialog-forms/create-collection-dialog.ts b/src/views-components/dialog-forms/create-collection-dialog.ts index 7ef6e4b3..d989d431 100644 --- a/src/views-components/dialog-forms/create-collection-dialog.ts +++ b/src/views-components/dialog-forms/create-collection-dialog.ts @@ -17,7 +17,13 @@ export const CreateCollectionDialog = compose( onSubmit: (data, dispatch) => { // Somehow an extra field called 'files' gets added, copy // the data object to get rid of it. - dispatch(createCollection({ ownerUuid: data.ownerUuid, name: data.name, description: data.description, storageClassesDesired: data.storageClassesDesired })); + dispatch(createCollection({ + ownerUuid: data.ownerUuid, + name: data.name, + description: data.description, + storageClassesDesired: data.storageClassesDesired, + properties: data.properties, + })); } }) )(DialogCollectionCreate); diff --git a/src/views-components/dialog-update/dialog-collection-update.tsx b/src/views-components/dialog-update/dialog-collection-update.tsx index cce64d27..5bdaa4f4 100644 --- a/src/views-components/dialog-update/dialog-collection-update.tsx +++ b/src/views-components/dialog-update/dialog-collection-update.tsx @@ -12,6 +12,9 @@ import { CollectionDescriptionField, CollectionStorageClassesField } from 'views-components/form-fields/collection-form-fields'; +import { UpdateCollectionPropertiesForm } from 'views-components/collection-properties/update-collection-properties-form'; +import { UpdateCollectionPropertiesList } from 'views-components/collection-properties/update-collection-properties-list'; +import { FormGroup, FormLabel } from '@material-ui/core'; type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps; @@ -26,5 +29,10 @@ export const DialogCollectionUpdate = (props: DialogCollectionProps) => const CollectionEditFields = () => + Properties + + + + ; diff --git a/src/views-components/dialog-update/dialog-project-update.tsx b/src/views-components/dialog-update/dialog-project-update.tsx index fda7c47d..96e6d927 100644 --- a/src/views-components/dialog-update/dialog-project-update.tsx +++ b/src/views-components/dialog-update/dialog-project-update.tsx @@ -9,6 +9,9 @@ import { ProjectUpdateFormDialogData } from 'store/projects/project-update-actio import { FormDialog } from 'components/form-dialog/form-dialog'; import { ProjectNameField, ProjectDescriptionField, UsersField } from 'views-components/form-fields/project-form-fields'; import { GroupClass } from 'models/group'; +import { FormGroup, FormLabel } from '@material-ui/core'; +import { UpdateProjectPropertiesForm } from 'views-components/project-properties/update-project-properties-form'; +import { UpdateProjectPropertiesList } from 'views-components/project-properties/update-project-properties-list'; type DialogProjectProps = WithDialogProps<{sourcePanel: GroupClass, create?: boolean}> & InjectedFormProps; @@ -35,6 +38,11 @@ export const DialogProjectUpdate = (props: DialogProjectProps) => { const ProjectEditFields = () => + Properties + + + + ; const GroupAddFields = () => diff --git a/src/views-components/project-properties/update-project-properties-form.tsx b/src/views-components/project-properties/update-project-properties-form.tsx new file mode 100644 index 00000000..e6e78e34 --- /dev/null +++ b/src/views-components/project-properties/update-project-properties-form.tsx @@ -0,0 +1,32 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { reduxForm, reset } from 'redux-form'; +import { withStyles } from '@material-ui/core'; +import { + PROJECT_UPDATE_PROPERTIES_FORM_NAME, + addPropertyToUpdateProjectForm +} from 'store/projects/project-update-actions'; +import { + ResourcePropertiesForm, + ResourcePropertiesFormData +} from 'views-components/resource-properties-form/resource-properties-form'; + +const Form = withStyles( + ({ spacing }) => ( + { container: + { + paddingTop: spacing.unit, + margin: 0, + } + }) + )(ResourcePropertiesForm); + +export const UpdateProjectPropertiesForm = reduxForm({ + form: PROJECT_UPDATE_PROPERTIES_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(addPropertyToUpdateProjectForm(data)); + dispatch(reset(PROJECT_UPDATE_PROPERTIES_FORM_NAME)); + } +})(Form); \ No newline at end of file diff --git a/src/views-components/project-properties/update-project-properties-list.tsx b/src/views-components/project-properties/update-project-properties-list.tsx new file mode 100644 index 00000000..5572af73 --- /dev/null +++ b/src/views-components/project-properties/update-project-properties-list.tsx @@ -0,0 +1,70 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + withStyles, + StyleRulesCallback, + WithStyles, +} from '@material-ui/core'; +import { RootState } from 'store/store'; +import { + removePropertyFromUpdateProjectForm, + PROJECT_UPDATE_FORM_SELECTOR, +} from 'store/projects/project-update-actions'; +import { ArvadosTheme } from 'common/custom-theme'; +import { getPropertyChip } from '../resource-properties-form/property-chip'; +import { ProjectProperties } from 'store/projects/project-create-actions'; + +type CssRules = 'tag'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + tag: { + marginRight: theme.spacing.unit, + marginBottom: theme.spacing.unit + } +}); + +interface UpdateProjectPropertiesListDataProps { + properties: ProjectProperties; +} + +interface UpdateProjectPropertiesListActionProps { + handleDelete: (key: string, value: string) => void; +} + +const mapStateToProps = (state: RootState): UpdateProjectPropertiesListDataProps => { + const properties = PROJECT_UPDATE_FORM_SELECTOR(state, 'properties'); + return { properties }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): UpdateProjectPropertiesListActionProps => ({ + handleDelete: (key: string, value: string) => dispatch(removePropertyFromUpdateProjectForm(key, value)) +}); + +type UpdateProjectPropertiesListProps = UpdateProjectPropertiesListDataProps & + UpdateProjectPropertiesListActionProps & WithStyles; + +const List = withStyles(styles)( + ({ classes, handleDelete, properties }: UpdateProjectPropertiesListProps) => +
+ {properties && + Object.keys(properties).map(k => + Array.isArray(properties[k]) + ? (properties[k] as string[]).map((v: string) => + getPropertyChip( + k, v, + () => handleDelete(k, v), + classes.tag)) + : getPropertyChip( + k, (properties[k] as string), + () => handleDelete(k, (properties[k] as string)), + classes.tag)) + } +
+); + +export const UpdateProjectPropertiesList = connect(mapStateToProps, mapDispatchToProps)(List); \ No newline at end of file diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index adc3e995..851008c0 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -220,7 +220,8 @@ export const CollectionPanel = withStyles(styles)( } handleContextMenu = (event: React.MouseEvent) => { - const { uuid, ownerUuid, name, description, kind, storageClassesDesired } = this.props.item; + const { uuid, ownerUuid, name, description, + kind, storageClassesDesired, properties } = this.props.item; const menuKind = this.props.dispatch(resourceUuidToContextMenuKind(uuid)); const resource = { uuid, @@ -230,6 +231,7 @@ export const CollectionPanel = withStyles(styles)( storageClassesDesired, kind, menuKind, + properties, }; // Avoid expanding/collapsing the panel event.stopPropagation(); diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx index 4a3f60a6..e08aea32 100644 --- a/src/views/project-panel/project-panel.tsx +++ b/src/views/project-panel/project-panel.tsx @@ -185,6 +185,7 @@ export const ProjectPanel = withStyles(styles)( menuKind, description: resource.description, storageClassesDesired: (resource as CollectionResource).storageClassesDesired, + properties: ('properties' in resource) ? resource.properties : {}, })); } this.props.dispatch(loadDetailsPanel(resourceUuid));