UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

447 lines (410 loc) 14.2 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 2.13.0 */ import React from "react"; import PropTypes from "prop-types"; import {DateTime} from "luxon"; import UserAvatar from "../../Common/Avatar/UserAvatar"; import GroupAvatar from "../../Common/Avatar/GroupAvatar"; import {withAppContext} from "../../../contexts/AppContext"; import {withResourceWorkspace} from "../../../contexts/ResourceWorkspaceContext"; import Icon from "../../../../shared/components/Icons/Icon"; import {Trans, withTranslation} from "react-i18next"; const LIMIT_ACTIVITIES_PER_PAGE = 5; class DisplayResourceFolderDetailsActivity extends React.Component { /** * Constructor * @param {Object} props */ constructor(props) { super(props); this.state = this.defaultState; this.bindCallbacks(); } /** * Get default state * @returns {*} */ get defaultState() { return { activities: null, // list of activities activitiesPage: 1, // pagination for activity loadingMore: false, // processing when the user want to see more activities open: false, loading: true, }; } /** * Bind callbacks methods */ bindCallbacks() { this.handleTitleClickEvent = this.handleTitleClickEvent.bind(this); this.handleMoreClickEvent = this.handleMoreClickEvent.bind(this); } /** * Whenever the component has updated in terms of props * @param prevProps */ async componentDidUpdate(prevProps) { await this.handleResourceChange(prevProps.resourceWorkspaceContext.details.folder); } /** * Check if the folder has changed and fetch * @param previousFolder */ async handleResourceChange(previousFolder) { if (this.state.open && this.folder.id !== previousFolder.id) { // Reset the component, and fetch activities for the new folder. const state = Object.assign({}, this.defaultState, {open: true}); await this.setState(state); await this.fetch(); this.setState({loading: false}); } } /** * handle when the users click on the section header. * Open/Close it. */ async handleTitleClickEvent() { // If the section is open, reset the component and close the section. if (this.state.open) { const defaultState = this.defaultState; this.setState(defaultState); } else { await this.setState({loading: true, open: true}); await this.fetch(); this.setState({loading: false}); } } /** * handle when the users click on the more button. */ async handleMoreClickEvent() { const activitiesPage = this.state.activitiesPage + 1; const loadingMore = true; await this.setState({activitiesPage, loadingMore}); await this.fetch(); this.setState({loadingMore: false}); } /** * Fetch the folder activities * @returns {Promise<void>} */ async fetch() { const limit = LIMIT_ACTIVITIES_PER_PAGE; const page = this.state.activitiesPage; const options = {limit, page}; const newActivities = await this.props.context.port.request("passbolt.actionlogs.find-all-for", "Folder", this.folder.id, options); let activities; // For the first page need to reset activities state if (this.state.activitiesPage > 1) { activities = [...(this.state.activities || []), ...newActivities]; } else { activities = [...newActivities]; } this.setState({activities}); } /** * Format date in time ago * @param {string} date The date to format * @return {string} */ formatDateTimeAgo(date) { const dateTime = DateTime.fromISO(date); const duration = dateTime.diffNow().toMillis(); return duration > -1000 && duration < 0 ? this.translate('Just now') : dateTime.toRelative({locale: this.props.context.locale}); } /** * Get an activity creator full name * @param {object} user The creator * @return string */ getActivityCreatorFullName(user) { return `${user.profile.first_name} ${user.profile.last_name}`; } /** * Get a folder permalink * @param {object} folder The target folder * @returns {string} */ getFolderPermalink(folder) { const baseUrl = this.props.context.userSettings.getTrustedDomain(); return `${baseUrl}/app/folders/view/${folder.id}`; } /** * Get a permission aro name * @param {object} permission The permission */ getPermissionAroName(permission) { if (permission.user) { const profile = permission.user.profile; return `${profile.first_name} ${profile.last_name}`; } else { return permission.group.name; } } /** * Get a permission aro name * @param {object} permission The permission */ getPermissionLabel(permission) { switch (permission.type) { case 1: return this.translate("can read"); case 7: return this.translate("can update"); case 15: return this.translate("is owner"); } } /** * Get permission change type label * @param {string} type The target change type */ getPermissionChangeTypeLabel(type) { switch (type) { case "created": return this.translate("new"); case "updated": return this.translate("updated"); case "removed": return this.translate("deleted"); } } /** * Render a created activity. * @param {object} activity The target activity * @returns {JSX} */ renderFolderCreatedActivity(activity) { const activityCreatorName = this.getActivityCreatorFullName(activity.creator); const folderPermalink = this.getFolderPermalink(this.folder); const folderName = this.folder.name; const activityFormattedDate = this.formatDateTimeAgo(activity.created); return ( <li key={activity.action_log_id} className="usercard-detailed-col-2"> <div className="content-wrapper"> <div className="content"> <div className="name"> <Trans> <span className="creator">{{activityCreatorName}}</span> created folder <a target="_blank" rel="noopener noreferrer" href={folderPermalink}>{{folderName}}</a> </Trans> </div> <div className="subinfo light">{activityFormattedDate}</div> </div> </div> <UserAvatar user={activity.creator} baseUrl={this.props.context.userSettings.getTrustedDomain()}/> </li> ); } /** * Render an updated activity. * @param {object} activity The target activity * @returns {JSX} */ renderFolderUpdatedActivity(activity) { const activityCreatorName = this.getActivityCreatorFullName(activity.creator); const folderPermalink = this.getFolderPermalink(this.folder); const folderName = this.folder.name; const activityFormattedDate = this.formatDateTimeAgo(activity.created); return ( <li key={activity.action_log_id} className="usercard-detailed-col-2"> <div className="content-wrapper"> <div className="content"> <div className="name"> <Trans> <span className="creator">{{activityCreatorName}}</span> updated folder <a target="_blank" rel="noopener noreferrer" href={folderPermalink}>{{folderName}}</a> </Trans> </div> <div className="subinfo light">{activityFormattedDate}</div> </div> </div> <UserAvatar user={activity.creator} baseUrl={this.props.context.userSettings.getTrustedDomain()}/> </li> ); } /** * Render a shared activity permission change item. * @param {object} permission The target permission * @param {string} changeType The change type * @returns {JSX} */ renderSharedActivityPermissionChangeItem(permission, changeType) { const permissionLabel = this.getPermissionLabel(permission); const permissionAroName = this.getPermissionAroName(permission); const changeTypeLabel = this.getPermissionChangeTypeLabel(changeType); return ( <li key={permission.id} className="clearfix"> {permission.user && <UserAvatar user={permission.user} baseUrl={this.props.context.userSettings.getTrustedDomain()}/> } {permission.group && <GroupAvatar group={permission.group} baseUrl={this.props.context.userSettings.getTrustedDomain()}/> } <div className="name"> <span className="creator">{permissionAroName}</span> <span className="permission-type"> - {permissionLabel}</span> </div> <div className="type"><span className={changeType}>{changeTypeLabel}</span></div> </li> ); } /** * Render a permissions updated activity. * @param {object} activity The target activity * @returns {JSX} */ renderPermissionsUpdatedActivity(activity) { const activityCreatorName = this.getActivityCreatorFullName(activity.creator); const folderPermalink = this.getFolderPermalink(this.folder); const folderName = this.folder.name; const activityFormattedDate = this.formatDateTimeAgo(activity.created); return ( <li key={activity.action_log_id} className="usercard-detailed-col-2"> <div className="content-wrapper"> <div className="content"> <div className="name"> <Trans> <span className="creator">{{activityCreatorName}}</span> changed permissions of folder <a target="_blank" rel="noopener noreferrer" href={folderPermalink}>{{folderName}}</a> with </Trans> </div> <div className="subinfo light">{activityFormattedDate}</div> <ul className="permissions-list"> {activity.data.permissions.added.map(permission => this.renderSharedActivityPermissionChangeItem(permission, "created"))} {activity.data.permissions.updated.map(permission => this.renderSharedActivityPermissionChangeItem(permission, "updated"))} {activity.data.permissions.removed.map(permission => this.renderSharedActivityPermissionChangeItem(permission, "removed"))} </ul> </div> </div> <UserAvatar user={activity.creator} baseUrl={this.props.context.userSettings.getTrustedDomain()}/> </li> ); } /** * Render an unknown activity. * @returns {JSX} */ renderUnknownActivity() { return ( <li className="usercard-detailed-col-2"> <div className="content-wrapper"> <div className="content"> <Trans>Unknown activity, please contact your administrator.</Trans> </div> </div> </li> ); } /** * Render an activity * @param {object} activity The activity to render */ renderActivity(activity) { let render; switch (activity.type) { case "Folders.created": { render = this.renderFolderCreatedActivity(activity); break; } case "Folders.updated": { render = this.renderFolderUpdatedActivity(activity); break; } case "Permissions.updated": { render = this.renderPermissionsUpdatedActivity(activity); break; } default: { render = this.renderUnknownActivity(activity); break; } } return render; } /** * Is the more button visible * @return {boolean} */ isMoreButtonVisible() { if (this.state.activities === null) { return false; } return !this.state.activities.some(activity => activity.type === "Folders.created"); } /** * Returns the current detailed folder */ get folder() { return this.props.resourceWorkspaceContext.details.folder; } /** * Get the translate function * @returns {function(...[*]=)} */ get translate() { return this.props.t; } /** * Render the component * @returns {JSX} */ render() { const loadingActivities = this.state.activities === null; const isMoreButtonVisible = this.isMoreButtonVisible(); return ( <div className={`activity accordion sidebar-section ${this.state.open ? "" : "closed"}`}> <div className="accordion-header"> <h4> <a onClick={this.handleTitleClickEvent} role="button"> <Trans>Activity</Trans> {this.state.open && <Icon name="caret-down"/> } {!this.state.open && <Icon name="caret-right"/> } </a> </h4> </div> <div className="accordion-content"> {loadingActivities && <div className="processing-wrapper"> <Icon name="spinner"/> <span className="processing-text"><Trans>Retrieving activities</Trans></span> </div> } {!loadingActivities && <React.Fragment> <ul className="ready"> {this.state.activities.map(activity => this.renderActivity(activity))} </ul> {isMoreButtonVisible && <div className="actions"> <a onClick={this.handleMoreClickEvent} className={`button action-logs-load-more ${this.state.loadingMore ? "processing disabled" : ""}`} role="button"> <span><Trans>More</Trans></span> </a> </div> } </React.Fragment> } </div> </div> ); } } DisplayResourceFolderDetailsActivity.propTypes = { context: PropTypes.any, // The application context resourceWorkspaceContext: PropTypes.any, t: PropTypes.func, // The translation function }; export default withAppContext(withResourceWorkspace(withTranslation('common')(DisplayResourceFolderDetailsActivity)));