passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
287 lines (261 loc) • 8.75 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 UserAvatar from "../../Common/Avatar/UserAvatar";
import { withActionFeedback } from "../../../contexts/ActionFeedbackContext";
import PropTypes from "prop-types";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import { withLoading } from "../../../contexts/LoadingContext";
import { Trans, withTranslation } from "react-i18next";
import CommentsServiceWorkerService from "../CommentsServiceWorkerService";
/**
* This component allows the current user to add a new comment on a resource
*/
class AddResourceComment extends React.Component {
/**
* Constructor
* @param {Object} props
*/
constructor(props) {
super(props);
this.state = this.getDefaultState();
this.bindCallbacks();
this.createRefs();
this.commentsServiceWorkerService = new CommentsServiceWorkerService(props.context.port);
}
/**
* ComponentDidMount
* Invoked immediately after component is inserted into the tree
* @return {void}
*/
componentDidMount() {
this.textareaRef.current.focus();
}
/**
* Get default state
* @returns {*}
*/
getDefaultState() {
return {
content: "", // The comment content
actions: {
// The ongoing action
processing: false, // An action is processing
},
errors: {}, // The list of validation errors
};
}
/**
* Bind callbacks methods
*/
bindCallbacks() {
this.handleSubmitEvent = this.handleSubmitEvent.bind(this);
this.handleCancelEvent = this.handleCancelEvent.bind(this);
this.handleContentChanged = this.handleContentChanged.bind(this);
this.handleEscapeKeyPressed = this.handleEscapeKeyPressed.bind(this);
}
/**
* Create DOM nodes or React elements references in order to be able to access them programmatically.
*/
createRefs() {
this.textareaRef = React.createRef();
}
/**
* Returns true if the form is valid
* @param {object} errors The errors
* @returns {boolean}
*/
isValid(errors) {
return Object.values(errors).every((value) => !value);
}
/**
* Handle the submitting of the new comment
* @param event The event object
*/
async handleSubmitEvent(event) {
// Prevent the default browser behavior to post the form.
event.preventDefault();
const errors = this.validate();
if (this.isValid(errors)) {
try {
this.setState({ actions: { processing: true } });
this.props.loadingContext.add();
const addedComment = await this.add();
await this.handleSubmitSuccess(addedComment);
} catch (error) {
await this.handleSubmitFailure(error);
}
}
}
/**
* Whenever the submit action has been successful
* @param addedComment The added comment
*/
async handleSubmitSuccess(addedComment) {
this.props.loadingContext.remove();
await this.props.actionFeedbackContext.displaySuccess(this.translate("The comment has been added successfully"));
this.props.onAdd(addedComment);
}
/**
* Whenever the submit action has not been successful
* @param error
* @returns {Promise<void>}
*/
async handleSubmitFailure(error) {
this.props.loadingContext.remove();
await this.props.actionFeedbackContext.displayError(error.message);
this.setState({
actions: { processing: false },
errors: { technicalError: error.message },
});
}
/**
* Handle the cancellation of the add of the comment
*/
handleCancelEvent() {
this.props.onCancel();
}
/**
* Whenever the content has changed
* @param event The DOM event
*/
handleContentChanged(event) {
this.setState({ content: event.target.value });
}
/**
* Whenever the user press the escape key
* @param event Keypressed event
*/
handleEscapeKeyPressed(event) {
// Close the dialog when the user presses the "ESC" key if the component is cancellable.
const hasEscapeKeyPressed = event.keyCode === 27;
const mustQuit = hasEscapeKeyPressed && this.props.cancellable;
if (mustQuit) {
// Stop the event propagation in order to avoid a parent component to react to this ESC event.
event.stopPropagation();
this.props.onCancel();
}
}
/**
* Add a new comment
* @returns {Promise<void>}
*/
async add() {
const commentToAdd = this.state.content.trim();
const payload = {
foreign_key: this.props.resource.id,
foreign_model: "Resource",
content: commentToAdd,
user_id: this.props.context.loggedInUser.id,
};
return await this.commentsServiceWorkerService.create(payload);
}
/**
* Validate the form.
* @return {object} errors
*/
validate() {
// Rule: the content could not be empty or trimmered empty
const isEmpty = this.state.content.trim() === "";
// Rule: the content could not be longer than 256
const isTooLong = this.state.content.length > 256;
const errors = { isEmpty, isTooLong };
this.setState({ errors });
return errors;
}
/**
* Get the translate function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Render the component
* @returns {JSX}
*/
render() {
return (
<form className="comment" autoComplete="off">
<div className="left-column">
<UserAvatar
user={this.props.context.loggedInUser}
baseUrl={this.props.context.siteSettings.settings.app.url}
className="author profile picture avatar"
/>
</div>
<div className="right-column">
<div className="form-content">
<div className="input textarea required">
<textarea
ref={this.textareaRef}
placeholder={this.translate("Add a comment")}
aria-required={true}
onChange={this.handleContentChanged}
onKeyDown={this.handleEscapeKeyPressed}
disabled={this.state.actions.processing}
></textarea>
<div className="error-message">
{this.state.errors.isEmpty && this.translate("A comment is required.")}
{this.state.errors.isTooLong && this.translate("A comment must be less than 256 characters")}
{this.state.errors.technicalError}
</div>
</div>
<div className="metadata">
<span className="author username">
<Trans>You</Trans>
</span>
<span className="modified">
<Trans>right now</Trans>
</span>
</div>
<div className="actions">
{this.props.cancellable && (
<button
type="button"
className="link cancel"
onClick={this.handleCancelEvent}
disabled={this.state.actions.processing}
>
<span>
<Trans>Cancel</Trans>
</span>
</button>
)}
<button
className="button primary comment-submit"
type="submit"
onClick={this.handleSubmitEvent}
disabled={this.state.actions.processing}
>
<Trans>Save</Trans>
</button>
</div>
</div>
</div>
</form>
);
}
}
AddResourceComment.propTypes = {
context: PropTypes.any, // The application context
resource: PropTypes.object, // The resource to which one add a comment
onAdd: PropTypes.func, // Called after the comment has been added
onCancel: PropTypes.func, // Called after the add operation has been cancelled
cancellable: PropTypes.bool, // Flag to determine if the user can cancel
actionFeedbackContext: PropTypes.any, // The action feedback context
loadingContext: PropTypes.any, // The loading context
t: PropTypes.func, // The translation function
};
export default withAppContext(withLoading(withActionFeedback(withTranslation("common")(AddResourceComment))));