diff --git a/.circleci/config.yml b/.circleci/config.yml index e629370d9..1515ee606 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -100,7 +100,7 @@ workflows: - test filters: branches: - only: ['dev', 'dev-msinteg'] + only: ['dev', 'dev-msinteg', 'feature/attachmentPermissions'] - deployTest02: requires: - test diff --git a/src/api/projectAttachments.js b/src/api/projectAttachments.js index 6ad90a879..afdbb7b50 100644 --- a/src/api/projectAttachments.js +++ b/src/api/projectAttachments.js @@ -13,6 +13,13 @@ export function addProjectAttachment(projectId, fileData) { } export function updateProjectAttachment(projectId, attachmentId, attachment) { + if (attachment && (!attachment.userIds || attachment.userIds.length === 0)) { + attachment = { + ...attachment, + userIds: null + } + } + return axios.patch( `${PROJECTS_API_URL}/v4/projects/${projectId}/attachments/${attachmentId}`, { param: attachment }) diff --git a/src/components/FileList/AddFilePermissions.jsx b/src/components/FileList/AddFilePermissions.jsx new file mode 100644 index 000000000..a9ee62ad1 --- /dev/null +++ b/src/components/FileList/AddFilePermissions.jsx @@ -0,0 +1,65 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Modal from 'react-modal' +import { mapKeys, get } from 'lodash' + +import UserAutoComplete from '../UserAutoComplete/UserAutoComplete' + +import './AddFilePermissions.scss' +import XMarkIcon from '../../assets/icons/icon-x-mark.svg' + +const AddFilePermission = ({ onCancel, onSubmit, onChange, selectedUsers, projectMembers, loggedInUser }) => { + selectedUsers = selectedUsers || '' + const mapHandlesToUserIds = handles => { + const projectMembersByHandle = mapKeys(projectMembers, value => value.handle) + return handles.filter(handle => handle).map(h => get(projectMembersByHandle[h], 'userId')) + } + + return ( + +
+
+ Who do you want to share this file with? + +
+ + {/* Share with all members */} +
+
+ +
+
+ + {/* Share with specific people */} +
+
OR ONLY SPECIFIC PEOPLE
+ + + +
+ +
+
+
+
+ ) +} + +AddFilePermission.propTypes = { + onCancel: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + selectedUsers: PropTypes.string, + projectMembers: PropTypes.object, + loggedInUser: PropTypes.object.isRequired +} + +export default AddFilePermission diff --git a/src/components/FileList/AddFilePermissions.scss b/src/components/FileList/AddFilePermissions.scss new file mode 100644 index 000000000..b362f5257 --- /dev/null +++ b/src/components/FileList/AddFilePermissions.scss @@ -0,0 +1,4 @@ +.btn-all-members { + text-align: center; + margin-top: 8px; +} \ No newline at end of file diff --git a/src/components/FileList/FileList.jsx b/src/components/FileList/FileList.jsx index 537f02d58..2ea427f70 100644 --- a/src/components/FileList/FileList.jsx +++ b/src/components/FileList/FileList.jsx @@ -7,7 +7,8 @@ import uncontrollable from 'uncontrollable' import FileDeletionConfirmModal from './FileDeletionConfirmModal' import './FileList.scss' -const FileList = ({files, onDelete, onSave, deletingFile, onDeleteIntent, canModify}) => ( +const FileList = ({files, onDelete, onSave, deletingFile, onDeleteIntent, canModify, projectMembers, + loggedInUser }) => ( {deletingFile &&
} { @@ -34,6 +35,8 @@ const FileList = ({files, onDelete, onSave, deletingFile, onDeleteIntent, canMod onDelete={ onDeleteIntent } onSave={ onSave } canModify={canModify} + projectMembers={projectMembers} + loggedInUser={loggedInUser} /> ) }) @@ -42,7 +45,9 @@ const FileList = ({files, onDelete, onSave, deletingFile, onDeleteIntent, canMod ) FileList.propTypes = { - canModify: PropTypes.bool.isRequired + canModify: PropTypes.bool.isRequired, + projectMembers: PropTypes.object.isRequired, + loggedInUser: PropTypes.object.isRequired } FileList.Item = FileListItem diff --git a/src/components/FileList/FileListItem.jsx b/src/components/FileList/FileListItem.jsx index 4cc8cbb49..a2f65c64c 100644 --- a/src/components/FileList/FileListItem.jsx +++ b/src/components/FileList/FileListItem.jsx @@ -10,6 +10,7 @@ import TrashIcon from '../../assets/icons/icon-trash.svg' import CloseIcon from '../../assets/icons/icon-close.svg' import EditIcon from '../../assets/icons/icon-edit.svg' import SaveIcon from '../../assets/icons/icon-save.svg' +import UserAutoComplete from '../UserAutoComplete/UserAutoComplete' export default class FileListItem extends React.Component { @@ -19,6 +20,7 @@ export default class FileListItem extends React.Component { this.state = { title: props.title, description: props.description, + userIds: props.userIds, isEditing: false } this.handleSave = this.handleSave.bind(this) @@ -27,6 +29,7 @@ export default class FileListItem extends React.Component { this.validateForm = this.validateForm.bind(this) this.validateTitle = this.validateTitle.bind(this) this.onTitleChange = this.onTitleChange.bind(this) + this.onUserIdChange = this.onUserIdChange.bind(this) } onDelete() { @@ -34,10 +37,11 @@ export default class FileListItem extends React.Component { } startEdit() { - const {title, description} = this.props + const {title, description, userIds} = this.props this.setState({ title, description, + userIds, isEditing: true }) } @@ -48,7 +52,7 @@ export default class FileListItem extends React.Component { if (!_.isEmpty(errors)) { this.setState({ errors }) } else { - this.props.onSave(this.props.id, {title, description: this.refs.desc.value}, e) + this.props.onSave(this.props.id, {title, description: this.refs.desc.value, userIds: this.state.userIds}, e) this.setState({isEditing: false}) } } @@ -74,9 +78,28 @@ export default class FileListItem extends React.Component { this.setState({ errors }) } + onUserIdChange(selectedHandles = '') { + this.setState({ + userIds: this.handlesToUserIds(selectedHandles.split(',')) + }) + } + + userIdsToHandles(userIds) { + const { projectMembers } = this.props + userIds = userIds || [] + return userIds.map(userId => _.get(projectMembers[userId], 'handle')) + } + + handlesToUserIds(handles) { + const { projectMembers } = this.props + const projectMembersByHandle = _.mapKeys(projectMembers, value => value.handle) + handles = handles || [] + return handles.filter(handle => handle).map(handle => _.get(projectMembersByHandle[handle], 'userId')) + } + renderEditing() { - const {title, description} = this.props - const { errors } = this.state + const { title, description, projectMembers, loggedInUser } = this.props + const { errors, userIds } = this.state const onExitEdit = () => this.setState({isEditing: false, errors: {} }) return (
@@ -90,6 +113,11 @@ export default class FileListItem extends React.Component { { (errors && errors.title) &&
{ errors.title }
}