diff --git a/src/components/AssetsLibrary/FilesGridView.jsx b/src/components/AssetsLibrary/FilesGridView.jsx index 2618fab04..a5ea04d09 100644 --- a/src/components/AssetsLibrary/FilesGridView.jsx +++ b/src/components/AssetsLibrary/FilesGridView.jsx @@ -10,6 +10,7 @@ import DeleteFileLinkModal from '../LinksMenu/DeleteFileLinkModal' import EditFileAttachment from '../LinksMenu/EditFileAttachment' import SubFolder from './SubFolder' import ItemOperations from './ItemOperations' +import UserTooltip from '../User/UserTooltip' import FolderIcon from '../../assets/icons/v.2.5/icon-folder-small.svg' @@ -28,6 +29,7 @@ const FilesGridView = ({ title, selectedUsers, onAddAttachment, + assetsMembers, isSharingAttachment, discardAttachments, onChangePermissions, @@ -78,6 +80,7 @@ const FilesGridView = ({ link={ subFolderContent } renderLink={ renderLink } goBack={goBack} + assetsMembers={assetsMembers} onDeletePostAttachment={onDeletePostAttachment} loggedInUser={loggedInUser} formatModifyDate={formatModifyDate} @@ -99,6 +102,7 @@ const FilesGridView = ({
  • Type
    Name
    +
    Created By
    Modified
  • @@ -119,12 +123,14 @@ const FilesGridView = ({ const canEdit = `${link.createdBy}` === `${loggedInUser.userId}` const changeSubFolder = () => onChangeSubFolder(link) + const owner = _.find(assetsMembers, m => m.userId === _.parseInt(link.createdBy)) if (Array.isArray(link.children) && link.children.length > 0) { return (
  • {formatFolderTitle(link.title)}

    +
    {formatModifyDate(link)}
  • ) @@ -161,6 +167,15 @@ const FilesGridView = ({

    {renderLink(link)}

    +
    + {!owner && (
    Unknown
    )} + {owner && ( +
    +
    + +
    +
    )} +
    {formatModifyDate(link)}
    {canEdit && ( diff --git a/src/components/AssetsLibrary/GridView.scss b/src/components/AssetsLibrary/GridView.scss index 7ab34c62b..638539628 100644 --- a/src/components/AssetsLibrary/GridView.scss +++ b/src/components/AssetsLibrary/GridView.scss @@ -85,7 +85,7 @@ } } -.item-modified { +.item-modified, .item-created-by { -webkit-box-flex: 0; -ms-flex: none; flex: none; @@ -134,4 +134,11 @@ .assets-gridview-container-active { position: relative; +} + +:global { + .user-block .tooltip-container { + text-align: left; + line-height: 20px; + } } \ No newline at end of file diff --git a/src/components/AssetsLibrary/LinksGridView.jsx b/src/components/AssetsLibrary/LinksGridView.jsx index 25fd4b45d..8f211347a 100644 --- a/src/components/AssetsLibrary/LinksGridView.jsx +++ b/src/components/AssetsLibrary/LinksGridView.jsx @@ -8,6 +8,7 @@ import DeleteLinkModal from '../LinksMenu/DeleteLinkModal' import EditLinkModal from '../LinksMenu/EditLinkModal' import SubFolder from './SubFolder' import ItemOperations from './ItemOperations' +import UserTooltip from '../User/UserTooltip' import FolderIcon from '../../assets/icons/v.2.5/icon-folder-small.svg' import LinkIcon from '../../assets/icons/link-12.svg' @@ -28,6 +29,7 @@ const LinksGridView = ({ title, formatModifyDate, formatFolderTitle, + assetsMembers, }) => { const renderLink = (link) => { if (link.onClick) { @@ -59,6 +61,7 @@ const LinksGridView = ({ renderLink={ renderLink } goBack={goBack} formatModifyDate={formatModifyDate} + assetsMembers={assetsMembers} isLinkSubFolder />)} {(!subFolderContent) && ( @@ -69,6 +72,7 @@ const LinksGridView = ({
  • Type
    Name
    +
    Created By
    Modified
  • @@ -87,12 +91,14 @@ const LinksGridView = ({ const onEditCancel = () => onEditIntent(-1) const handleEditClick = () => onEditIntent(idx) const changeSubFolder = () => onChangeSubFolder(link) + const owner = _.find(assetsMembers, m => m.userId === _.parseInt(link.createdBy)) if (Array.isArray(link.children) && link.children.length > 0) { return (
  • {formatFolderTitle(link.title)}

    +
    {formatModifyDate(link)}
  • ) @@ -119,6 +125,16 @@ const LinksGridView = ({
  • {renderLink(link)}

    +
    + {!owner && !link.createdBy && (
    )} + {!owner && link.createdBy && (
    Unknown
    )} + {owner && ( +
    +
    + +
    +
    )} +
    {formatModifyDate(link)}
    {(canEdit || canDelete) && ( diff --git a/src/components/AssetsLibrary/SubFolder.jsx b/src/components/AssetsLibrary/SubFolder.jsx index e8680cbe4..a23cac3ba 100644 --- a/src/components/AssetsLibrary/SubFolder.jsx +++ b/src/components/AssetsLibrary/SubFolder.jsx @@ -1,9 +1,11 @@ import React from 'react' import PropTypes from 'prop-types' +import _ from 'lodash' import cn from 'classnames' import DeleteFileLinkModal from '../LinksMenu/DeleteFileLinkModal' import ItemOperations from './ItemOperations' +import UserTooltip from '../User/UserTooltip' import FolderIcon from '../../assets/icons/v.2.5/icon-folder-small.svg' import './GridView.scss' @@ -54,7 +56,7 @@ class SubFolder extends React.Component { } render() { - const { link, renderLink, goBack, formatModifyDate, isLinkSubFolder } = this.props + const { link, renderLink, goBack, formatModifyDate, isLinkSubFolder, assetsMembers } = this.props const { linkToDelete } = this.state return (
    = 0)}, '')}> @@ -64,17 +66,20 @@ class SubFolder extends React.Component {
  • Type
    Name
    +
    Created By
    Modified
  • ..
    +
  • { link.children.map((childLink, i) => { + const owner = _.find(assetsMembers, m => m.userId === _.parseInt(childLink.createdBy)) if (linkToDelete === i) { return (
  • @@ -100,6 +105,16 @@ class SubFolder extends React.Component { return (
  • {renderLink(childLink)}

    +
    + {!owner && childLink.createdBy !== 'CoderBot' && (
    Unknown
    )} + {!owner && childLink.createdBy === 'CoderBot' && (
    CoderBot
    )} + {owner && ( +
    +
    + +
    +
    )} +
    {formatModifyDate(childLink)}
    {childLink.deletable && this.hasAccess(childLink.createdBy) && ( diff --git a/src/projects/detail/containers/AssetsInfoContainer.jsx b/src/projects/detail/containers/AssetsInfoContainer.jsx index b0a19d47c..6ee6fc61a 100644 --- a/src/projects/detail/containers/AssetsInfoContainer.jsx +++ b/src/projects/detail/containers/AssetsInfoContainer.jsx @@ -10,6 +10,7 @@ import LinksGridView from '../../../components/AssetsLibrary/LinksGridView' import FilesGridView from '../../../components/AssetsLibrary/FilesGridView' import AssetsStatistics from '../../../components/AssetsLibrary/AssetsStatistics' import { updateProject, deleteProject } from '../../actions/project' +import { loadMembers } from '../../../actions/members' import { loadDashboardFeeds, loadProjectMessages } from '../../actions/projectTopics' import { loadTopic } from '../../../actions/topics' import { loadProjectPlan } from '../../actions/projectPlan' @@ -68,6 +69,7 @@ class AssetsInfoContainer extends React.Component { !_.isEqual(nextProps.attachmentsAwaitingPermission, this.props.attachmentsAwaitingPermission) || !_.isEqual(nextProps.attachmentPermissions, this.props.attachmentPermissions) || !_.isEqual(nextProps.isSharingAttachment, this.props.isSharingAttachment) || + !_.isEqual(nextProps.assetsMembers, this.props.assetsMembers) || !_.isEqual(nextState.activeAssetsType, this.state.activeAssetsType) || !_.isEqual(nextState.ifModalOpen, this.state.ifModalOpen) } @@ -167,7 +169,11 @@ class AssetsInfoContainer extends React.Component { } onAddNewLink(link) { - const { updateProject, project } = this.props + const { updateProject, project, loggedInUser } = this.props + link.createdAt = moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') + link.createdBy = loggedInUser.userId + link.updatedAt = moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') + link.updatedBy = loggedInUser.userId updateProject(project.id, { bookmarks: update(project.bookmarks || [], { $push: [link] }) }) @@ -181,10 +187,14 @@ class AssetsInfoContainer extends React.Component { } onEditLink(idx, title, address) { - const { updateProject, project } = this.props + const { updateProject, project, loggedInUser } = this.props + const updatedAt = moment().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]') + const updatedBy = loggedInUser.userId const updatedLink = { title, - address + address, + updatedAt, + updatedBy } updateProject(project.id, { bookmarks: update(project.bookmarks, { $splice: [[idx, 1, updatedLink]] }) @@ -223,7 +233,7 @@ class AssetsInfoContainer extends React.Component { this.props.uploadProjectAttachments(project.id, attachment) } - extractHtmlLink(str) { + extractHtmlLink(str, userId) { const links = [] const regex = /]+href="(.*?)"[^>]*>([\s\S]*?)<\/a>/gm const urlRegex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/gm // eslint-disable-line no-useless-escape @@ -238,7 +248,8 @@ class AssetsInfoContainer extends React.Component { if (urlRegex.test(address)) { links.push({ title, - address + address, + createdBy: userId }) } @@ -249,7 +260,7 @@ class AssetsInfoContainer extends React.Component { return links } - extractMarkdownLink(str) { + extractMarkdownLink(str, userId) { const links = [] const regex = /(?:__|[*#])|\[(.*?)\]\((.*?)\)/gm const urlRegex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/gm // eslint-disable-line no-useless-escape @@ -264,7 +275,8 @@ class AssetsInfoContainer extends React.Component { if (urlRegex.test(address)) { links.push({ title, - address + address, + createdBy: userId }) } @@ -275,7 +287,7 @@ class AssetsInfoContainer extends React.Component { return links } - extractRawLink(str) { + extractRawLink(str, userId) { let links = [] const regex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,}[\s])(\s|$)/igm // eslint-disable-line no-useless-escape const rawLinks = str.match(regex) @@ -289,7 +301,8 @@ class AssetsInfoContainer extends React.Component { return { title: name, - address: url + address: url, + createdBy: userId } }) } @@ -303,9 +316,9 @@ class AssetsInfoContainer extends React.Component { let childrenLinks = [] feed.posts.forEach(post => { childrenLinks = childrenLinks.concat([ - ...this.extractHtmlLink(post.rawContent), - ...this.extractMarkdownLink(post.rawContent), - ...this.extractRawLink(post.rawContent) + ...this.extractHtmlLink(post.rawContent, post.userId), + ...this.extractMarkdownLink(post.rawContent, post.userId), + ...this.extractRawLink(post.rawContent, post.userId) ]) }) @@ -382,22 +395,9 @@ class AssetsInfoContainer extends React.Component { } } - render() { - const { project, currentMemberRole, isSuperUser, phases, feeds, - isManageUser, phasesTopics, projectTemplates, hideLinks, - attachmentsAwaitingPermission, addProjectAttachment, discardAttachments, attachmentPermissions, - changeAttachmentPermission, projectMembers, loggedInUser, isSharingAttachment, canAccessPrivatePosts } = this.props - const { ifModalOpen } = this.state - - const canManageLinks = !!currentMemberRole || isSuperUser - - let devices = [] - const primaryTarget = _.get(project, 'details.appDefinition.primaryTarget') - if (primaryTarget && !primaryTarget.seeAttached) { - devices.push(primaryTarget.value) - } else { - devices = _.get(project, 'details.devices', []) - } + getLinksAndAttachments() { + const { project, isSuperUser, phases, feeds, + isManageUser, phasesTopics, canAccessPrivatePosts } = this.props let attachments = project.attachments // merges the product attachments to show in the links menu @@ -444,16 +444,6 @@ class AssetsInfoContainer extends React.Component { }) ) - const attachmentsStorePath = `${PROJECT_ATTACHMENTS_FOLDER}/${project.id}/` - let enableFileUpload = true - if(project.version !== 'v2') { - const templateId = _.get(project, 'templateId') - const projectTemplate = _.find(projectTemplates, template => template.id === templateId) - enableFileUpload = _.some(projectTemplate.scope.sections, section => { - return _.some(section.subSections, subSection => subSection.id === 'files') - }) - } - // extract links from posts const topicLinks = this.extractLinksFromPosts(feeds) const publicTopicLinks = topicLinks.filter(link => link.tag !== PROJECT_FEED_TYPE_MESSAGES) @@ -475,6 +465,65 @@ class AssetsInfoContainer extends React.Component { ...this.extractAttachmentLinksFromPosts(phaseFeeds) ] + return ({ + links, + attachments, + }) + } + + componentDidMount() { + const {loadMembers } = this.props + const {links, attachments} = this.getLinksAndAttachments() + + let tmpUserIds = [] + let userIds = [] + _.forEach(links, link => { + tmpUserIds = _.union(tmpUserIds, _.map(link.children, 'createdBy')) + tmpUserIds = _.union(tmpUserIds, [link.createdBy]) + tmpUserIds = _.union(tmpUserIds, [link.updatedBy]) + }) + + _.forEach(attachments, attachment => { + tmpUserIds = _.union(tmpUserIds, _.map(attachment.children, 'createdBy')) + tmpUserIds = _.union(tmpUserIds, [attachment.createdBy]) + }) + + _.forEach(tmpUserIds, userId => { + userIds = _.union(userIds, [_.parseInt(userId)]) + }) + _.remove(userIds, i => !i) + + loadMembers(userIds) + } + + render() { + const { project, currentMemberRole, isSuperUser, projectTemplates, hideLinks, + attachmentsAwaitingPermission, addProjectAttachment, discardAttachments, attachmentPermissions, + changeAttachmentPermission, projectMembers, loggedInUser, isSharingAttachment, assetsMembers } = this.props + const { ifModalOpen } = this.state + + const canManageLinks = !!currentMemberRole || isSuperUser + + let devices = [] + const primaryTarget = _.get(project, 'details.appDefinition.primaryTarget') + if (primaryTarget && !primaryTarget.seeAttached) { + devices.push(primaryTarget.value) + } else { + devices = _.get(project, 'details.devices', []) + } + + const {links, attachments} = this.getLinksAndAttachments() + + const attachmentsStorePath = `${PROJECT_ATTACHMENTS_FOLDER}/${project.id}/` + let enableFileUpload = true + if(project.version !== 'v2') { + const templateId = _.get(project, 'templateId') + const projectTemplate = _.find(projectTemplates, template => template.id === templateId) + enableFileUpload = _.some(projectTemplate.scope.sections, section => { + return _.some(section.subSections, subSection => subSection.id === 'files') + }) + } + const assetsData = [] enableFileUpload && assetsData.push({name: 'Files', total: _.toString(attachments.length)}) !hideLinks && assetsData.push({name: 'Links', total: _.toString(links.length)}) @@ -597,6 +646,7 @@ class AssetsInfoContainer extends React.Component { onChangePermissions={changeAttachmentPermission} selectedUsers={attachmentPermissions} projectMembers={projectMembers} + assetsMembers={assetsMembers} pendingAttachments={attachmentsAwaitingPermission} loggedInUser={loggedInUser} attachmentsStorePath={attachmentsStorePath} @@ -607,6 +657,7 @@ class AssetsInfoContainer extends React.Component { {(!hideLinks && activeAssetsType === 'Links') && { attachmentPermissions: projectState.attachmentPermissions, isSharingAttachment: projectState.processingAttachments, projectMembers: _.keyBy(projectMembers, 'userId'), + assetsMembers: _.keyBy(members.members, 'userId'), loggedInUser: loadUser.user, canAccessPrivatePosts }) } -const mapDispatchToProps = { updateProject, deleteProject, addProjectAttachment, updateProjectAttachment, +const mapDispatchToProps = { updateProject, deleteProject, loadMembers, addProjectAttachment, updateProjectAttachment, loadProjectMessages, discardAttachments, uploadProjectAttachments, loadDashboardFeeds, loadTopic, changeAttachmentPermission, removeProjectAttachment, loadProjectPlan, saveFeedComment } diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js index 6abe03f07..8b7e28993 100644 --- a/src/projects/reducers/project.js +++ b/src/projects/reducers/project.js @@ -40,6 +40,7 @@ const initialState = { project: { invites: [] // invites are pushed directly into it hence need to declare first }, + assetsMembers: {}, projectNonDirty: {}, updateExisting: false, phases: null,