passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
393 lines (358 loc) • 13.4 kB
JavaScript
/**
* 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, { Fragment } from "react";
import PropTypes from "prop-types";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import { ResourceWorkspaceFilterTypes, withResourceWorkspace } from "../../../contexts/ResourceWorkspaceContext";
import { withRouter } from "react-router-dom";
import { Trans, withTranslation } from "react-i18next";
import { withRbac } from "../../../../shared/context/Rbac/RbacContext";
import { uiActions } from "../../../../shared/services/rbacs/uiActionEnumeration";
import { withPasswordExpiry } from "../../../contexts/PasswordExpirySettingsContext";
import { formatDateTimeAgo, formatExpirationDateTimeAgo } from "../../../../shared/utils/dateUtils";
import AttentionSVG from "../../../../img/svg/attention.svg";
import ShareFolderSVG from "../../../../img/svg/share_folder.svg";
import FolderSVG from "../../../../img/svg/folder.svg";
import CaretDownSVG from "../../../../img/svg/caret_down.svg";
import CaretRightSVG from "../../../../img/svg/caret_right.svg";
import TooltipPortal from "../../Common/Tooltip/TooltipPortal";
import CabinetSVG from "../../../../img/svg/cabinet.svg";
class DisplayResourceDetailsInformation extends React.Component {
/**
* Constructor
* @param {Object} props
*/
constructor(props) {
super(props);
this.state = this.getDefaultState();
this.bindCallbacks();
}
/**
* Get default state
* @returns {*}
*/
getDefaultState() {
return {
open: false,
creator: null, // the data of the resource creator
modifier: null, // the data of the resource creator
};
}
/**
* Bind callbacks methods
*/
bindCallbacks() {
this.handleFolderParentClickEvent = this.handleFolderParentClickEvent.bind(this);
this.handleTitleClickEvent = this.handleTitleClickEvent.bind(this);
this.isFolderParentShared = this.isFolderParentShared.bind(this);
}
componentDidMount() {
this.props.passwordExpiryContext.findSettings();
if (this.state.open) {
this.loadUserInformation();
}
}
/**
* Whenever the component has updated in terms of props
* @param prevProps
*/
componentDidUpdate(prevProps) {
this.handleResourceChange(prevProps.resourceWorkspaceContext.details.resource);
}
/**
* Loads the information about the creator and the modifier of the current selected resource.
* @returns {Promise<void>}
*/
async loadUserInformation() {
const resourceInformation = await this.props.context.port.request(
"passbolt.resources.find-details",
this.resource.id,
);
const hasInformationChanged =
this.resource.created_by !== resourceInformation.created_by ||
this.resource.modified_by !== resourceInformation.modified_by;
if (hasInformationChanged) {
//current selected resource might have changed and the information received doesn't match anymore. In such case we don't update the state.
return;
}
this.setState({
creator: resourceInformation?.creator,
modifier: resourceInformation?.modifier,
});
}
/**
* Check if the resource has changed and fetch
* @param previousResource
*/
handleResourceChange(previousResource) {
const hasResourceChanged = this.resource.id !== previousResource.id;
const hasResourceUpdated = this.resource.modified !== previousResource.modified;
if ((hasResourceChanged || hasResourceUpdated) && this.state.open) {
this.setState({ plaintextSecretDto: null, previewedSecret: null });
}
if (!hasResourceChanged) {
return;
}
const hasModifierOrCreatorChanged =
this.resource.created_by !== previousResource.created_by ||
this.resource.modified_by !== previousResource.modified_by;
if (!hasModifierOrCreatorChanged) {
return;
}
this.setState({ creator: null, modifier: null });
if (this.state.open) {
this.loadUserInformation();
}
}
/**
* Get the currently selected resource from workspace context
* @returns {object} resource dto
*/
get resource() {
return this.props.resourceWorkspaceContext.details.resource;
}
/**
* Handle when the user selects the folder parent.
*/
handleFolderParentClickEvent() {
if (this.resource.folder_parent_id) {
// Case of specific folder
const folderParent = this.props.context.folders.find((item) => item.id === this.resource.folder_parent_id);
const filterIsDifferent = this.props.resourceWorkspaceContext.filter.payload?.folder?.id !== folderParent.id;
if (filterIsDifferent) {
this.props.history.push(`/app/folders/view/${folderParent.id}`);
}
} else {
// Case of root folder
const filter = { type: ResourceWorkspaceFilterTypes.ROOT_FOLDER };
this.props.history.push(`/app/passwords`, { filter });
}
}
/**
* Handle when the user selects the folder parent.
*/
handleTitleClickEvent() {
const open = !this.state.open;
this.setState({ open });
if (!open) {
this.setState({ creator: null, modifier: null });
} else {
this.loadUserInformation();
}
}
/**
* Get the folder name.
* @param {string} folderParentId The folder parent id
* @returns {string}
*/
getFolderName(folderParentId) {
if (folderParentId === null) {
return this.translate("My workspace");
}
if (this.props.context.folders) {
const folder = this.props.context.folders.find((item) => item.id === folderParentId);
if (folder) {
return folder.name;
}
}
return "";
}
/**
* Check if folder parent is shared
* @returns {boolean}
*/
isFolderParentShared() {
const isShared = false;
if (this.resource.folder_parent_id !== null && this.props.context.folders) {
const folder = this.props.context.folders.find((item) => item.id === this.resource.folder_parent_id);
if (folder) {
return !folder.personal;
}
}
return isShared;
}
/**
* Returns true if the resource requires some attention.
* @returns {boolean}
*/
get isAttentionRequired() {
return this.isAttentionRequiredOnExpiryDate;
}
/**
* Returns the expiration status of the current resource.
* @returns {string}
*/
get resourceExpirationStatus() {
if (!this.resource?.expired) {
return this.translate("Not set");
}
return formatExpirationDateTimeAgo(this.resource?.expired, this.props.t, this.props.context.locale);
}
/**
* Returns true if the current resource is expired.
* @returns {boolean}
*/
get isAttentionRequiredOnExpiryDate() {
if (!this.resource?.expired) {
return false;
}
const expiryDate = new Date(this.resource.expired);
return expiryDate <= new Date();
}
/**
* Get the translate function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Render the tooltip folders structure.
* @param {array<FolderEntity>} folderStructure The folders structure.
* @render {JSX}
*/
renderTooltipFolderStructure(folderStructure) {
if (folderStructure.length === 0) {
return (
<span>
<Trans>My workspace</Trans>
</span>
);
}
return folderStructure?.map((folder, index) => (
<div key={folder.id} className="folder-level" style={{ marginLeft: `${5 * index}px` }}>
{folder.folder_parent_id !== null && <span className="caret">›</span>}
<span>{folder.name}</span>
</div>
));
}
/**
* Render the component
* @returns {JSX}
*/
render() {
const canUseFolders =
this.props.context.siteSettings.canIUse("folders") && this.props.rbacContext.canIUseAction(uiActions.FOLDERS_USE);
const canUsePasswordExpiry = this.props.passwordExpiryContext.isFeatureEnabled();
const creatorUsername = this.state.creator?.username || "";
const modifierUsername = this.state.modifier?.username || "";
const createdDateTimeAgo = formatDateTimeAgo(this.resource.created, this.props.t, this.props.context.locale);
const modifiedDateTimeAgo = formatDateTimeAgo(this.resource.modified, this.props.t, this.props.context.locale);
const folderStructure = this.props.context.getHierarchyFolderCache(this.resource.folder_parent_id);
return (
<div className={`detailed-information accordion sidebar-section ${this.state.open ? "" : "closed"}`}>
<div className="accordion-header">
<h4>
<button className="link no-border" type="button" onClick={this.handleTitleClickEvent}>
<span className="accordion-title">
<Trans>Information</Trans>
{canUsePasswordExpiry && this.isAttentionRequired && <AttentionSVG className="attention-required" />}
</span>
{this.state.open ? <CaretDownSVG /> : <CaretRightSVG />}
</button>
</h4>
</div>
{this.state.open && (
<div className="accordion-content">
<div className="information-label">
<span className="created label">
<Trans>Created</Trans>
</span>
<span className="created-by label">
<Trans>Created by</Trans>
</span>
<span className="modified label">
<Trans>Modified</Trans>
</span>
<span className="modified-by label">
<Trans>Modified by</Trans>
</span>
{canUseFolders && (
<span className="location label">
<Trans>Location</Trans>
</span>
)}
{canUsePasswordExpiry && (
<div className="expiry label label-with-icon">
<span className="ellipsis">
<Trans>Expiry</Trans>
</span>
{this.isAttentionRequiredOnExpiryDate && <AttentionSVG className="attention-required" />}
</div>
)}
</div>
<div className="information-value">
<span className="created value" title={this.resource.created}>
{createdDateTimeAgo}
</span>
<span className="created-by value">{creatorUsername}</span>
<span className="modified value" title={this.resource.modified}>
{modifiedDateTimeAgo}
</span>
<span className="modified-by value">{modifierUsername}</span>
{canUseFolders && (
<span className="location value">
<TooltipPortal message={this.renderTooltipFolderStructure(folderStructure)}>
<button
type="button"
onClick={this.handleFolderParentClickEvent}
disabled={!this.props.context.folders}
className="no-border"
>
{this.resource.folder_parent_id === null && (
<>
<CabinetSVG />
<span>
<Trans>My workspace</Trans>
</span>
</>
)}
{this.resource.folder_parent_id !== null && (
<>
{this.isFolderParentShared() ? <ShareFolderSVG /> : <FolderSVG />}
{folderStructure.map((folder) => (
<Fragment key={folder.id}>
{folder.folder_parent_id !== null && <span className="caret">›</span>}
<span>{folder.name}</span>
</Fragment>
))}
</>
)}
</button>
</TooltipPortal>
</span>
)}
{canUsePasswordExpiry && <span className="expiry value">{this.resourceExpirationStatus}</span>}
</div>
</div>
)}
</div>
);
}
}
DisplayResourceDetailsInformation.propTypes = {
context: PropTypes.any, // The application context
rbacContext: PropTypes.any, // The role based access control context
onSelectFolderParent: PropTypes.func,
onSelectRoot: PropTypes.func,
history: PropTypes.object,
resourceWorkspaceContext: PropTypes.object,
passwordExpiryContext: PropTypes.object, // the passowrd expiry context
t: PropTypes.func, // The translation function
};
export default withAppContext(
withRbac(
withRouter(withResourceWorkspace(withPasswordExpiry(withTranslation("common")(DisplayResourceDetailsInformation)))),
),
);