passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
503 lines (464 loc) • 17.3 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 from "react";
import SpinnerSVG from "../../../../img/svg/spinner.svg";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";
import { UserWorkspaceFilterTypes, withUserWorkspace } from "../../../contexts/UserWorkspaceContext";
import { withContextualMenu } from "../../../contexts/ContextualMenuContext";
import FilterUsersByGroupContextualMenu from "./FilterUsersByGroupContextualMenu";
import DisplayGroupContextualMenu from "./DisplayGroupContextualMenu";
import { Trans, withTranslation } from "react-i18next";
import CarretDownSVG from "../../../../img/svg/caret_down.svg";
import CarretRightSVG from "../../../../img/svg/caret_right.svg";
import UsersSVG from "../../../../img/svg/users.svg";
import MoreHorizontalSVG from "../../../../img/svg/more_horizontal.svg";
import { withDrag } from "../../../contexts/DragContext";
import AddUserToGroup from "../../UserGroup/AddUserToGroupDialog/AddUserToGroupDialog";
import { withDialog } from "../../../contexts/DialogContext";
/**
* This component display groups to filter the users
*/
class FilterUsersByGroup extends React.Component {
/**
* Constructor
* @param {Object} props
*/
constructor(props) {
super(props);
this.state = this.defaultState;
this.bindCallbacks();
}
/**
* Get default state
* @returns {*}
*/
get defaultState() {
return {
open: true, // open the group section
title: this.translate("All groups"), // title of the section
filterType: null, // type of the filter selected
moreTitleMenuOpen: false, // more title menu open
moreMenuOpenGroupId: null, // more menu open for a group with the id
dragOverGroupId: null, // ID of the group currently being dragged over
};
}
/**
* Bind callbacks methods
*/
bindCallbacks() {
this.handleTitleClickEvent = this.handleTitleClickEvent.bind(this);
this.handleTitleMoreClickEvent = this.handleTitleMoreClickEvent.bind(this);
this.handleCloseTitleMoreMenu = this.handleCloseTitleMoreMenu.bind(this);
this.handleTitleContextualMenuEvent = this.handleTitleContextualMenuEvent.bind(this);
this.handleFilterGroupType = this.handleFilterGroupType.bind(this);
this.handleGroupSelected = this.handleGroupSelected.bind(this);
this.handleMoreClickEvent = this.handleMoreClickEvent.bind(this);
this.handleCloseMoreMenu = this.handleCloseMoreMenu.bind(this);
this.handleContextualMenuEvent = this.handleContextualMenuEvent.bind(this);
this.handleDragLeaveTitle = this.handleDragLeaveTitle.bind(this);
this.handleDragOverTitle = this.handleDragOverTitle.bind(this);
this.handleDropTitle = this.handleDropTitle.bind(this);
this.isGroupDisabled = this.isGroupDisabled.bind(this);
this.isGroupDraggedOver = this.isGroupDraggedOver.bind(this);
}
/**
* Handle when the user click on the title.
*/
handleTitleClickEvent() {
const open = !this.state.open;
this.setState({ open });
}
/**
* Handle when the user requests to display the contextual menu on the groups title.
* @param {ReactEvent} event The event
*/
handleTitleContextualMenuEvent(event) {
// Prevent the browser contextual menu to pop up.
event.preventDefault();
this.showContextualMenu(event.pageY, event.pageX);
}
/**
* Handle when the user requests to display the contextual menu on the groups title section.
* @param {ReactEvent} event The event
*/
handleTitleMoreClickEvent(event) {
const moreTitleMenuOpen = !this.state.moreTitleMenuOpen;
this.setState({ moreTitleMenuOpen });
if (moreTitleMenuOpen) {
const { left, top } = event.currentTarget.getBoundingClientRect();
this.showContextualMenu(top + 18, left, "right");
}
}
/**
* Close the title more menu
*/
handleCloseTitleMoreMenu() {
this.setState({ moreTitleMenuOpen: false });
}
/**
* Handle when the user clicks right on a group
* @param {ReactEvent} event The event
* @param {Object} selectedTag The target group
*/
handleContextualMenuEvent(event, group) {
// Prevent the browser contextual menu to pop up.
event.preventDefault();
const isGroupManager = group.my_group_user && group.my_group_user.is_admin;
// No operation available if user is not a group manager or an admin
if (this.isCurrentUserAdmin || isGroupManager) {
const top = event.pageY;
const left = event.pageX;
const contextualMenuProps = { group, left, top };
this.props.contextualMenuContext.show(DisplayGroupContextualMenu, contextualMenuProps);
}
}
/**
*
*/
/**
* Handle the selection of a group
* @param event The Dom event
* @param group The selected group
*/
handleGroupSelected(event, group) {
const { id } = group;
this.props.history.push(`/app/groups/view/${id}`);
}
/**
* Handle when the user wants to filter tags
* @param {string} filterType
*/
handleFilterGroupType(filterType) {
this.setState({ filterType }, () => {
this.updateTitle();
});
}
/**
* Open the contextual menu for the current group
* @param event The DOM event
* @param group An user group
*/
handleMoreClickEvent(event, group) {
const moreMenuOpenGroupId = this.state.moreMenuOpenGroupId === group.id ? null : group.id;
this.setState({ moreMenuOpenGroupId });
if (moreMenuOpenGroupId) {
const { left, top } = event.currentTarget.getBoundingClientRect();
const onBeforeHide = this.handleCloseMoreMenu;
const contextualMenuProps = { group, left, top: top + 18, className: "right", onBeforeHide };
this.props.contextualMenuContext.show(DisplayGroupContextualMenu, contextualMenuProps);
}
}
/**
* Close the more menu with the group id
* @param {string} id The group id
*/
handleCloseMoreMenu(id) {
if (this.state.moreMenuOpenGroupId === id) {
this.setState({ moreMenuOpenGroupId: null });
}
}
/**
* Handle when the user is not dragging over the section title anymore.
* @param {ReactEvent} event The event
* @param {Object} group The group
*/
handleDragLeaveTitle(event, group) {
// Clear the drag over state for this group
if (this.state.dragOverGroupId === group.id) {
this.setState({ dragOverGroupId: null });
}
}
/**
* Handle when the user is dragging content over the section title.
* @param {ReactEvent} event The event
* @param {Object} group The group being dragged over
*/
handleDragOverTitle(event, group) {
// Prevent drop on disabled groups
if (this.isGroupDisabled(group)) {
return;
}
/*
* If you want to allow a drop, you must prevent the default handling by cancelling both the dragenter and dragover events.
* see: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
*/
event.preventDefault();
// Set the drag over state for this group
if (this.state.dragOverGroupId !== group.id) {
this.setState({ dragOverGroupId: group.id });
}
}
/**
* Handle when the user is dropping the content on the title.
* @param event The DOM event
* @param group An user group
*/
async handleDropTitle(event, group) {
// Prevent drop on disabled groups
if (this.isGroupDisabled(group)) {
return;
}
// Clear the drag over state
this.setState({ dragOverGroupId: null });
const users = this.props.dragContext.draggedItems.users;
const addUserToGroupDialogProps = {
user: users[0],
group: group,
};
this.props.context.setContext({ addUserToGroupDialogProps });
this.props.dialogContext.open(AddUserToGroup);
}
// Zero conditional statements
/**
* get the title
* @returns {{manage: string, default: string, member: string}}
*/
get titles() {
return {
[filterByGroupsOptions.manage]: this.translate("Groups I manage"),
[filterByGroupsOptions.member]: this.translate("Groups I am member of"),
default: this.translate("All groups"),
};
}
/**
* get the filter according to the type of the filter
* @returns {{manage: (function(*): *), all: (function(*): *), member: (function(*): *)}}
*/
get filters() {
return {
[filterByGroupsOptions.manage]: (group) => group.my_group_user && group.my_group_user.is_admin,
[filterByGroupsOptions.member]: (group) => group.my_group_user && !group.my_group_user.is_admin,
[filterByGroupsOptions.all]: (group) => group,
};
}
/**
* filter tag to display only the type selected
* @returns {*[filtered tags]}
*/
get filteredGroups() {
const filterType = this.state.filterType || filterByGroupsOptions.all;
const filter = this.filters[filterType];
return this.groupsSorted.filter(filter);
}
/**
* Returns true if the current user is admin
*/
get isCurrentUserAdmin() {
return this.props.context.loggedInUser && this.props.context.loggedInUser.role.name === "admin";
}
/**
* get groups
* @returns {*}
*/
get groups() {
return this.props.context.groups;
}
/**
* get groups sorted
* @returns {*|boolean}
*/
get groupsSorted() {
return this.groups.sort((groupA, groupB) => groupA.name.localeCompare(groupB.name));
}
/**
* Show the contextual menu
* @param {int} left The left position to display the menu
* @param {int} top The top position to display the menu
* @param {string} className The class name to display the menu
*/
showContextualMenu(top, left, className = "") {
const onFilterSelected = this.handleFilterGroupType;
const onBeforeHide = this.handleCloseTitleMoreMenu;
const contextualMenuProps = { left, onFilterSelected, onBeforeHide, top, className };
this.props.contextualMenuContext.show(FilterUsersByGroupContextualMenu, contextualMenuProps);
}
/**
* update the title of the filter tag
*/
updateTitle() {
const title = this.titles[this.state.filterType] || this.titles.default;
this.setState({ title });
}
/**
* check if component loading
* @returns {boolean}
*/
isLoading() {
return this.groups === null;
}
/**
* has at least one group
* @returns {*|boolean}
*/
hasGroup() {
return this.filteredGroups.length > 0;
}
/**
* Returns true if the given group is selected
* @param group A group
*/
isSelected(group) {
const isGroupFilter = this.props.userWorkspaceContext.filter.type === UserWorkspaceFilterTypes.GROUP;
const filterPayload = this.props.userWorkspaceContext.filter.payload;
const groupPayload = filterPayload && filterPayload.group;
const isGroupSelected = () => groupPayload.id === group.id;
return isGroupFilter && isGroupSelected();
}
/**
* Returns true if the contextual menu More can be shown
* @param group The selected group
*/
canShowMore(group) {
const isGroupManager = group.my_group_user && group.my_group_user.is_admin;
return this.isCurrentUserAdmin || isGroupManager;
}
/**
* Check if a group should be disabled during drag
* @param {Object} group The group to check
* @returns {boolean} True if the group should be disabled
*/
isGroupDisabled(group) {
const { dragging, draggedItems } = this.props.dragContext;
const disabledGroupIds = draggedItems?.disabledGroupIds;
return dragging && disabledGroupIds && disabledGroupIds.includes(group.id);
}
/**
* Check if a group is currently being dragged over
* @param {Object} group The group to check
* @returns {boolean} True if the group is being dragged over
*/
isGroupDraggedOver(group) {
return this.state.dragOverGroupId === group.id;
}
/**
* Get the translate function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Render the component
* @returns {JSX}
*/
render() {
return (
<div className="navigation-secondary-tree navigation-secondary navigation-groups accordion">
<ul className="accordion-header">
<li className={`node root ${this.state.open ? "open" : "close"}`}>
<div className="row title">
<div className="main-cell-wrapper">
<div className="main-cell">
<h3 className="section-title">
<span className="folders-label" onClick={this.handleTitleClickEvent}>
<button type="button" className="link no-border">
<div className="toggle-folder">{this.state.open ? <CarretDownSVG /> : <CarretRightSVG />}</div>
<UsersSVG />
<span>{this.state.title}</span>
</button>
</span>
</h3>
</div>
</div>
<div className="dropdown right-cell more-ctrl">
<button
type="button"
className={`button-transparent inline-menu-horizontal ${this.state.moreTitleMenuOpen ? "open" : ""}`}
onClick={this.handleTitleMoreClickEvent}
>
<MoreHorizontalSVG />
</button>
</div>
</div>
</li>
</ul>
{this.state.open && (
<div className="accordion-content">
{this.isLoading() && (
<div className="processing-wrapper">
<SpinnerSVG />
<span className="processing-text">
<Trans>Retrieving groups</Trans>
</span>
</div>
)}
{!this.isLoading() && !this.hasGroup() && (
<em className="empty-content">
<Trans>empty</Trans>
</em>
)}
{!this.isLoading() && this.hasGroup() && (
<ul className="navigation-secondary-tree ready">
{this.filteredGroups.map((group) => (
<li className="open node root group-item" key={group.id}>
<div
className={`row ${this.isSelected(group) ? "selected" : ""} ${this.isGroupDisabled(group) ? "disabled" : ""} ${this.isGroupDraggedOver(group) ? "drop-focus" : ""}`}
>
<div
className="main-cell-wrapper"
onClick={(event) => this.handleGroupSelected(event, group)}
onContextMenu={(event) => this.handleContextualMenuEvent(event, group)}
onDragOver={(event) => this.handleDragOverTitle(event, group)}
onDragLeave={(event) => this.handleDragLeaveTitle(event, group)}
onDrop={(event) => this.handleDropTitle(event, group)}
>
<div className="main-cell">
<button type="button" className="link no-border" title={group.name}>
<span className="ellipsis group-name">{group.name}</span>
</button>
</div>
</div>
{this.canShowMore(group) && (
<div className="dropdown right-cell more-ctrl">
<button
type="button"
onClick={(event) => this.handleMoreClickEvent(event, group)}
className={`button-transparent inline-menu-horizontal ${this.state.moreMenuOpenGroupId === group.id ? "open" : ""}`}
>
<MoreHorizontalSVG />
</button>
</div>
)}
</div>
</li>
))}
</ul>
)}
</div>
)}
</div>
);
}
}
FilterUsersByGroup.propTypes = {
context: PropTypes.any, // The application context
userWorkspaceContext: PropTypes.any, // user workspace context
history: PropTypes.object,
contextualMenuContext: PropTypes.any, // The contextual menu context
dragContext: PropTypes.any,
dialogContext: PropTypes.any,
t: PropTypes.func, // The translation function
};
export default withAppContext(
withRouter(
withUserWorkspace(withContextualMenu(withDrag(withDialog(withTranslation("common")(FilterUsersByGroup))))),
),
);
export const filterByGroupsOptions = {
all: "all",
manage: "manage",
member: "member",
};