[MM-61716][MM-62643] Introduce Modal component, remove bootstrap and react-bootstrap from most modals, update styles (#3317)
* Introduce Modal, add some styles from the webapp * Remove bootstrap from LoadingScreen/WelcomeScreen * Migrate add/edit server modal to Modal, remove bootstrap * Migrate remove server modal to Modal, remove bootstrap * Migrate certificate modal to Modal, remove bootstrap * Migrate login and permission modals to Modal, remove Bootstrap * Misc fixes * E2E tests for current modals * Update src/renderer/components/Modal.tsx Co-authored-by: Daniel Espino García <larkox@gmail.com> * Update src/renderer/components/Modal.tsx Co-authored-by: Daniel Espino García <larkox@gmail.com> * PR feedback --------- Co-authored-by: Daniel Espino García <larkox@gmail.com> Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
@@ -3,20 +3,23 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Button, Modal} from 'react-bootstrap';
|
||||
|
||||
import {Modal} from './Modal';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
title: string;
|
||||
body: React.ReactNode;
|
||||
acceptLabel: string;
|
||||
cancelLabel: string;
|
||||
onHide: () => void;
|
||||
onAccept: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onCancel: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onAccept: () => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
export default function DestructiveConfirmationModal(props: Props) {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
acceptLabel,
|
||||
@@ -27,23 +30,18 @@ export default function DestructiveConfirmationModal(props: Props) {
|
||||
...rest} = props;
|
||||
return (
|
||||
<Modal
|
||||
onHide={onHide}
|
||||
id={id}
|
||||
onExited={onHide}
|
||||
isDeleteModal={true}
|
||||
modalHeaderText={title}
|
||||
handleCancel={onCancel}
|
||||
handleConfirm={onAccept}
|
||||
confirmButtonText={acceptLabel}
|
||||
cancelButtonText={cancelLabel}
|
||||
confirmButtonClassName='btn-danger'
|
||||
{...rest}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>{title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
{body}
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
variant='link'
|
||||
onClick={onCancel}
|
||||
>{cancelLabel}</Button>
|
||||
<Button
|
||||
variant='danger'
|
||||
onClick={onAccept}
|
||||
>{acceptLabel}</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
274
src/renderer/components/Modal.tsx
Normal file
274
src/renderer/components/Modal.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {useState, useRef, useEffect, useCallback} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import 'renderer/css/components/Modal.scss';
|
||||
|
||||
export type Props = {
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
onExited: () => void;
|
||||
|
||||
className?: string;
|
||||
modalHeaderText?: React.ReactNode;
|
||||
modalSubheaderText?: React.ReactNode;
|
||||
show?: boolean;
|
||||
handleCancel?: () => void;
|
||||
handleConfirm?: () => void;
|
||||
handleEnterKeyPress?: () => void;
|
||||
handleKeydown?: (event?: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
confirmButtonText?: React.ReactNode;
|
||||
confirmButtonClassName?: string;
|
||||
cancelButtonText?: React.ReactNode;
|
||||
cancelButtonClassName?: string;
|
||||
isConfirmDisabled?: boolean;
|
||||
isDeleteModal?: boolean;
|
||||
autoCloseOnCancelButton?: boolean;
|
||||
autoCloseOnConfirmButton?: boolean;
|
||||
ariaLabel?: string;
|
||||
errorText?: string | React.ReactNode;
|
||||
tabIndex?: number;
|
||||
autoFocusConfirmButton?: boolean;
|
||||
headerInput?: React.ReactNode;
|
||||
bodyPadding?: boolean;
|
||||
bodyDivider?: boolean;
|
||||
footerContent?: React.ReactNode;
|
||||
footerDivider?: boolean;
|
||||
appendedContent?: React.ReactNode;
|
||||
headerButton?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const Modal: React.FC<Props> = ({
|
||||
id = 'modal',
|
||||
children,
|
||||
onExited,
|
||||
className,
|
||||
modalHeaderText,
|
||||
modalSubheaderText,
|
||||
show = true,
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
handleEnterKeyPress,
|
||||
handleKeydown,
|
||||
confirmButtonText,
|
||||
confirmButtonClassName,
|
||||
cancelButtonText,
|
||||
cancelButtonClassName,
|
||||
isConfirmDisabled,
|
||||
isDeleteModal,
|
||||
autoCloseOnCancelButton = true,
|
||||
autoCloseOnConfirmButton = true,
|
||||
ariaLabel,
|
||||
errorText,
|
||||
tabIndex,
|
||||
autoFocusConfirmButton,
|
||||
headerInput,
|
||||
bodyPadding = true,
|
||||
bodyDivider,
|
||||
footerContent,
|
||||
footerDivider,
|
||||
appendedContent,
|
||||
headerButton,
|
||||
}) => {
|
||||
const [showState, setShowState] = useState<boolean>();
|
||||
const backdropRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setShowState(show ?? true);
|
||||
}, [show]);
|
||||
|
||||
const onHide = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
backdropRef.current?.addEventListener('transitionend', () => {
|
||||
resolve();
|
||||
}, {once: true});
|
||||
setShowState(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = useCallback(async () => {
|
||||
await onHide();
|
||||
onExited();
|
||||
}, [onExited]);
|
||||
|
||||
const handleCancelClick = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
if (autoCloseOnCancelButton) {
|
||||
await onHide();
|
||||
}
|
||||
handleCancel?.();
|
||||
};
|
||||
|
||||
const handleConfirmClick = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
if (autoCloseOnConfirmButton) {
|
||||
await onHide();
|
||||
}
|
||||
handleConfirm?.();
|
||||
};
|
||||
|
||||
const onEnterKeyDown = async (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (event.nativeEvent.isComposing) {
|
||||
return;
|
||||
}
|
||||
if (autoCloseOnConfirmButton) {
|
||||
await onHide();
|
||||
}
|
||||
if (handleEnterKeyPress) {
|
||||
handleEnterKeyPress();
|
||||
}
|
||||
}
|
||||
handleKeydown?.(event);
|
||||
};
|
||||
|
||||
let confirmButton;
|
||||
if (handleConfirm) {
|
||||
const isConfirmOrDeleteClassName = isDeleteModal ? 'delete' : 'confirm';
|
||||
let confirmButtonTextNode: React.ReactNode = (
|
||||
<FormattedMessage
|
||||
id='modal.confirm'
|
||||
defaultMessage='Confirm'
|
||||
/>
|
||||
);
|
||||
if (confirmButtonText) {
|
||||
confirmButtonTextNode = confirmButtonText;
|
||||
}
|
||||
|
||||
confirmButton = (
|
||||
<button
|
||||
id={`${id}_confirm`}
|
||||
autoFocus={autoFocusConfirmButton}
|
||||
type='submit'
|
||||
className={classNames('Modal__button btn btn-primary', isConfirmOrDeleteClassName, confirmButtonClassName, {
|
||||
disabled: isConfirmDisabled,
|
||||
})}
|
||||
onClick={handleConfirmClick}
|
||||
disabled={isConfirmDisabled}
|
||||
>
|
||||
{confirmButtonTextNode}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
let cancelButton;
|
||||
if (handleCancel) {
|
||||
let cancelButtonTextNode: React.ReactNode = (
|
||||
<FormattedMessage
|
||||
id='modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
);
|
||||
if (cancelButtonText) {
|
||||
cancelButtonTextNode = cancelButtonText;
|
||||
}
|
||||
|
||||
cancelButton = (
|
||||
<button
|
||||
id={`${id}_cancel`}
|
||||
type='button'
|
||||
className={classNames('Modal__button btn btn-tertiary', cancelButtonClassName)}
|
||||
onClick={handleCancelClick}
|
||||
>
|
||||
{cancelButtonTextNode}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const headerText = modalHeaderText && (
|
||||
<div className='Modal__header'>
|
||||
<h1
|
||||
id='modalLabel'
|
||||
className='Modal_title'
|
||||
>
|
||||
{modalHeaderText}
|
||||
</h1>
|
||||
{headerButton}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={backdropRef}
|
||||
className={classNames('Modal_backdrop fade', {show: showState})}
|
||||
/>
|
||||
<div
|
||||
role='dialog'
|
||||
className={classNames('Modal fade', {show: showState})}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
id={id}
|
||||
role='dialog'
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabel ? undefined : 'modalLabel'}
|
||||
className={classNames(
|
||||
'Modal_dialog Modal__compassDesign',
|
||||
className,
|
||||
)}
|
||||
onClick={useCallback((event) => event.stopPropagation(), [])}
|
||||
>
|
||||
<div
|
||||
onKeyDown={onEnterKeyDown}
|
||||
tabIndex={tabIndex || 0}
|
||||
className='Modal_content'
|
||||
>
|
||||
<div className='Modal_header'>
|
||||
<div className='Modal__header__text_container'>
|
||||
{headerText}
|
||||
{headerInput}
|
||||
{
|
||||
modalSubheaderText &&
|
||||
<div className='Modal_subheading-container'>
|
||||
<p
|
||||
id='Modal_subHeading'
|
||||
className='Modal_subheading'
|
||||
>
|
||||
{modalSubheaderText}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
type='button'
|
||||
className='close'
|
||||
onClick={onClose}
|
||||
>
|
||||
<span aria-hidden='true'>{'×'}</span>
|
||||
<span className='sr-only'>{'Close'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className={classNames('Modal_body', {divider: bodyDivider})}>
|
||||
{errorText && (
|
||||
<div className='Modal_error'>
|
||||
<i className='icon icon-alert-outline'/>
|
||||
<span>{errorText}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={classNames('Modal__body', {padding: bodyPadding})}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
{(cancelButton || confirmButton || footerContent) && (
|
||||
<div className={classNames('Modal_footer', {divider: footerDivider})}>
|
||||
{(cancelButton || confirmButton) ? (
|
||||
<>
|
||||
{cancelButton}
|
||||
{confirmButton}
|
||||
</>
|
||||
) : (
|
||||
footerContent
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{Boolean(appendedContent) && appendedContent}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -3,7 +3,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Modal, Button, FormGroup, FormControl, FormLabel, FormText, Spinner} from 'react-bootstrap';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import {FormattedMessage, injectIntl} from 'react-intl';
|
||||
|
||||
@@ -14,20 +13,22 @@ import type {UniqueServer} from 'types/config';
|
||||
import type {Permissions} from 'types/permissions';
|
||||
import type {URLValidationResult} from 'types/server';
|
||||
|
||||
import Input, {SIZE, STATUS} from './Input';
|
||||
import {Modal} from './Modal';
|
||||
|
||||
import 'renderer/css/components/NewServerModal.scss';
|
||||
|
||||
type Props = {
|
||||
onClose?: () => void;
|
||||
onClose: () => void;
|
||||
onSave?: (server: UniqueServer, permissions?: Permissions) => void;
|
||||
server?: UniqueServer;
|
||||
permissions?: Permissions;
|
||||
editMode?: boolean;
|
||||
show?: boolean;
|
||||
restoreFocus?: boolean;
|
||||
currentOrder?: number;
|
||||
setInputRef?: (inputRef: HTMLInputElement) => void;
|
||||
intl: IntlShape;
|
||||
prefillURL?: string;
|
||||
unremoveable?: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -49,10 +50,6 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
validationTimeout?: NodeJS.Timeout;
|
||||
mounted: boolean;
|
||||
|
||||
static defaultProps = {
|
||||
restoreFocus: true,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
@@ -159,19 +156,13 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
|
||||
getServerURLMessage = () => {
|
||||
if (this.state.validationStarted) {
|
||||
return (
|
||||
<div>
|
||||
<Spinner
|
||||
className='NewServerModal-validationSpinner'
|
||||
animation='border'
|
||||
size='sm'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.validating'
|
||||
defaultMessage='Validating...'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.INFO,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.validating',
|
||||
defaultMessage: 'Validating...',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.state.validationResult) {
|
||||
@@ -180,114 +171,78 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
|
||||
switch (this.state.validationResult?.status) {
|
||||
case URLValidationStatus.Missing:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='error'
|
||||
>
|
||||
<i className='icon-close-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlRequired'
|
||||
defaultMessage='URL is required.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.ERROR,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlRequired',
|
||||
defaultMessage: 'URL is required.',
|
||||
}),
|
||||
};
|
||||
case URLValidationStatus.Invalid:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='error'
|
||||
>
|
||||
<i className='icon-close-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.urlIncorrectFormatting'
|
||||
defaultMessage='URL is not formatted correctly.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.ERROR,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.urlIncorrectFormatting',
|
||||
defaultMessage: 'URL is not formatted correctly.',
|
||||
}),
|
||||
};
|
||||
case URLValidationStatus.URLExists:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.serverUrlExists'
|
||||
defaultMessage='A server named {serverName} with the same Site URL already exists.'
|
||||
values={{serverName: this.state.validationResult.existingServerName}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.WARNING,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.serverUrlExists',
|
||||
defaultMessage: 'A server named {serverName} with the same Site URL already exists.',
|
||||
}, {
|
||||
serverName: this.state.validationResult.existingServerName,
|
||||
}),
|
||||
};
|
||||
case URLValidationStatus.Insecure:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.insecure'
|
||||
defaultMessage='Your server URL is potentially insecure. For best results, use a URL with the HTTPS protocol.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.WARNING,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.warning.insecure',
|
||||
defaultMessage: 'Your server URL is potentially insecure. For best results, use a URL with the HTTPS protocol.',
|
||||
}),
|
||||
};
|
||||
case URLValidationStatus.NotMattermost:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.notMattermost'
|
||||
defaultMessage='The server URL provided does not appear to point to a valid Mattermost server. Please verify the URL and check your connection.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.WARNING,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.warning.notMattermost',
|
||||
defaultMessage: 'The server URL provided does not appear to point to a valid Mattermost server. Please verify the URL and check your connection.',
|
||||
}),
|
||||
};
|
||||
case URLValidationStatus.URLNotMatched:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='warning'
|
||||
>
|
||||
<i className='icon-alert-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.urlNotMatched'
|
||||
defaultMessage='The server URL does not match the configured Site URL on your Mattermost server. Server version: {serverVersion}'
|
||||
values={{serverVersion: this.state.validationResult.serverVersion}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.WARNING,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.warning.urlNotMatched',
|
||||
defaultMessage: 'The server URL does not match the configured Site URL on your Mattermost server. Server version: {serverVersion}',
|
||||
}, {
|
||||
serverVersion: this.state.validationResult.serverVersion,
|
||||
}),
|
||||
};
|
||||
case URLValidationStatus.URLUpdated:
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='info'
|
||||
>
|
||||
<i className='icon-information-outline'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.warning.urlUpdated'
|
||||
defaultMessage='The server URL provided has been updated to match the configured Site URL on your Mattermost server. Server version: {serverVersion}'
|
||||
values={{serverVersion: this.state.validationResult.serverVersion}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.INFO,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.warning.urlUpdated',
|
||||
defaultMessage: 'The server URL provided has been updated to match the configured Site URL on your Mattermost server. Server version: {serverVersion}',
|
||||
}, {
|
||||
serverVersion: this.state.validationResult.serverVersion,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id='urlValidation'
|
||||
className='success'
|
||||
>
|
||||
<i className='icon-check-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.success.ok'
|
||||
defaultMessage='Server URL is valid. Server version: {serverVersion}'
|
||||
values={{serverVersion: this.state.validationResult.serverVersion}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.SUCCESS,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.success.ok',
|
||||
defaultMessage: 'Server URL is valid. Server version: {serverVersion}',
|
||||
}, {
|
||||
serverVersion: this.state.validationResult.serverVersion,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
openNotificationPrefs = () => {
|
||||
@@ -303,19 +258,18 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
getServerNameMessage = () => {
|
||||
if (!this.state.validationResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.state.serverName.length) {
|
||||
return (
|
||||
<div
|
||||
id='nameValidation'
|
||||
className='error'
|
||||
>
|
||||
<i className='icon-close-circle'/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.error.nameRequired'
|
||||
defaultMessage='Name is required.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
type: STATUS.ERROR,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.error.nameRequired',
|
||||
defaultMessage: 'Name is required.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -401,114 +355,71 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
bsClass='modal'
|
||||
className='NewServerModal'
|
||||
show={this.props.show}
|
||||
id='newServerModal'
|
||||
enforceFocus={true}
|
||||
onEntered={() => this.serverUrlInputRef?.focus()}
|
||||
onHide={this.props.onClose}
|
||||
restoreFocus={this.props.restoreFocus}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
this.save();
|
||||
|
||||
// The add button from behind this might still be focused
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case 'Escape':
|
||||
this.props.onClose?.();
|
||||
break;
|
||||
}
|
||||
}}
|
||||
className='NewServerModal'
|
||||
onExited={this.props.unremoveable ? () => {} : this.props.onClose}
|
||||
modalHeaderText={this.getModalTitle()}
|
||||
confirmButtonText={this.getSaveButtonLabel()}
|
||||
handleConfirm={this.save}
|
||||
isConfirmDisabled={!this.state.serverName.length || !this.state.validationResult || this.isServerURLErrored()}
|
||||
handleCancel={this.props.onClose}
|
||||
bodyDivider={true}
|
||||
footerDivider={true}
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{this.getModalTitle()}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body>
|
||||
<>
|
||||
{!(this.props.editMode && this.props.server?.isPredefined) &&
|
||||
<>
|
||||
<form>
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.serverURL'
|
||||
defaultMessage='Server URL'
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl
|
||||
id='serverUrlInput'
|
||||
type='text'
|
||||
value={this.state.serverUrl}
|
||||
placeholder='https://example.com'
|
||||
onChange={this.handleServerUrlChange}
|
||||
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
ref={(ref: HTMLInputElement) => {
|
||||
this.serverUrlInputRef = ref;
|
||||
if (this.props.setInputRef) {
|
||||
this.props.setInputRef(ref);
|
||||
}
|
||||
}}
|
||||
isInvalid={this.isServerURLErrored()}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<FormControl.Feedback/>
|
||||
<FormText>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.serverURL.description'
|
||||
defaultMessage='The URL of your Mattermost server. Must start with http:// or https://.'
|
||||
/>
|
||||
</FormText>
|
||||
</FormGroup>
|
||||
<FormGroup className='NewServerModal-noBottomSpace'>
|
||||
<FormLabel>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.serverDisplayName'
|
||||
defaultMessage='Server Display Name'
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl
|
||||
id='serverNameInput'
|
||||
type='text'
|
||||
value={this.state.serverName}
|
||||
placeholder={this.props.intl.formatMessage({id: 'renderer.components.newServerModal.serverDisplayName', defaultMessage: 'Server Display Name'})}
|
||||
onChange={this.handleServerNameChange}
|
||||
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
isInvalid={!this.state.serverName.length}
|
||||
/>
|
||||
<FormControl.Feedback/>
|
||||
<FormText className='NewServerModal-noBottomSpace'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.serverDisplayName.description'
|
||||
defaultMessage='The name of the server displayed on your desktop app tab bar.'
|
||||
/>
|
||||
</FormText>
|
||||
</FormGroup>
|
||||
</form>
|
||||
<div
|
||||
className='NewServerModal-validation'
|
||||
>
|
||||
{this.getServerNameMessage()}
|
||||
{this.getServerURLMessage()}
|
||||
</div>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
id='serverUrlInput'
|
||||
name='url'
|
||||
type='text'
|
||||
inputSize={SIZE.LARGE}
|
||||
value={this.state.serverUrl}
|
||||
onChange={this.handleServerUrlChange}
|
||||
customMessage={this.getServerURLMessage() ?? ({
|
||||
type: STATUS.INFO,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.serverURL.description',
|
||||
defaultMessage: 'The URL of your Mattermost server. Must start with http:// or https://.',
|
||||
}),
|
||||
})}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.serverURL',
|
||||
defaultMessage: 'Server URL',
|
||||
})}
|
||||
/>
|
||||
<Input
|
||||
id='serverNameInput'
|
||||
name='name'
|
||||
type='text'
|
||||
inputSize={SIZE.LARGE}
|
||||
value={this.state.serverName}
|
||||
onChange={this.handleServerNameChange}
|
||||
customMessage={this.getServerNameMessage() ?? ({
|
||||
type: STATUS.INFO,
|
||||
value: this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.serverDisplayName.description',
|
||||
defaultMessage: 'The name of the server displayed on your desktop app tab bar.',
|
||||
}),
|
||||
})}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'renderer.components.newServerModal.serverDisplayName',
|
||||
defaultMessage: 'Server display name',
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{this.props.editMode &&
|
||||
<>
|
||||
<hr/>
|
||||
<h5>
|
||||
<h3 className='NewServerModal__permissions__title'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.permissions.title'
|
||||
defaultMessage='Permissions'
|
||||
/>
|
||||
</h5>
|
||||
</h3>
|
||||
<Toggle
|
||||
isChecked={this.state.permissions.media?.allowed}
|
||||
onChange={this.handleChangePermission('media')}
|
||||
@@ -520,7 +431,7 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
defaultMessage='Microphone and Camera'
|
||||
/>
|
||||
{this.state.cameraDisabled &&
|
||||
<FormText>
|
||||
<small className='NewServerModal__toggle__description'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.permissions.microphoneAndCamera.windowsCameraPermissions'
|
||||
defaultMessage='Camera is disabled in Windows Settings. Click <link>here</link> to open the Camera Settings.'
|
||||
@@ -535,10 +446,10 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</FormText>
|
||||
</small>
|
||||
}
|
||||
{this.state.microphoneDisabled &&
|
||||
<FormText>
|
||||
<small className='NewServerModal__toggle__description'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.permissions.microphoneAndCamera.windowsMicrophoneaPermissions'
|
||||
defaultMessage='Microphone is disabled in Windows Settings. Click <link>here</link> to open the Microphone Settings.'
|
||||
@@ -553,7 +464,7 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</FormText>
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
</Toggle>
|
||||
@@ -568,22 +479,22 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
defaultMessage='Notifications'
|
||||
/>
|
||||
{window.process.platform === 'darwin' &&
|
||||
<FormText>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.permissions.notifications.mac'
|
||||
defaultMessage='You may also need to enable notifications in macOS for Mattermost. Click <link>here</link> to open the System Preferences.'
|
||||
values={notificationValues}
|
||||
/>
|
||||
</FormText>
|
||||
<small className='NewServerModal__toggle__description'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.permissions.notifications.mac'
|
||||
defaultMessage='You may also need to enable notifications in macOS for Mattermost. Click <link>here</link> to open the System Preferences.'
|
||||
values={notificationValues}
|
||||
/>
|
||||
</small>
|
||||
}
|
||||
{window.process.platform === 'win32' &&
|
||||
<FormText>
|
||||
<small className='NewServerModal__toggle__description'>
|
||||
<FormattedMessage
|
||||
id='renderer.components.newServerModal.permissions.notifications.windows'
|
||||
defaultMessage='You may also need to enable notifications in Windows for Mattermost. Click <link>here</link> to open the Notification Settings.'
|
||||
values={notificationValues}
|
||||
/>
|
||||
</FormText>
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
</Toggle>
|
||||
@@ -609,33 +520,7 @@ class NewServerModal extends React.PureComponent<Props, State> {
|
||||
</Toggle>
|
||||
</>
|
||||
}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
{this.props.onClose &&
|
||||
<Button
|
||||
id='cancelNewServerModal'
|
||||
onClick={this.props.onClose}
|
||||
variant='link'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='label.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
{this.props.onSave &&
|
||||
<Button
|
||||
id='saveNewServerModal'
|
||||
onClick={this.save}
|
||||
disabled={!this.state.serverName.length || !this.state.validationResult || this.isServerURLErrored()}
|
||||
variant='primary'
|
||||
>
|
||||
{this.getSaveButtonLabel()}
|
||||
</Button>
|
||||
}
|
||||
</Modal.Footer>
|
||||
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@@ -10,36 +10,32 @@ import DestructiveConfirmationModal from './DestructiveConfirmModal';
|
||||
|
||||
type Props = {
|
||||
show: boolean;
|
||||
serverName: string;
|
||||
onHide: () => void;
|
||||
onAccept: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onCancel: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onAccept: () => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
function RemoveServerModal(props: Props) {
|
||||
const intl = useIntl();
|
||||
const {serverName, ...rest} = props;
|
||||
const {...rest} = props;
|
||||
return (
|
||||
<DestructiveConfirmationModal
|
||||
{...rest}
|
||||
id='removeServerModal'
|
||||
title={intl.formatMessage({id: 'renderer.components.removeServerModal.title', defaultMessage: 'Remove Server'})}
|
||||
acceptLabel={intl.formatMessage({id: 'label.remove', defaultMessage: 'Remove'})}
|
||||
cancelLabel={intl.formatMessage({id: 'label.cancel', defaultMessage: 'Cancel'})}
|
||||
body={(
|
||||
<Modal.Body>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='renderer.components.removeServerModal.body'
|
||||
defaultMessage='This will remove the server from your Desktop App but will not delete any of its data - you can add the server back to the app at any time.'
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='renderer.components.removeServerModal.confirm'
|
||||
defaultMessage='Confirm you wish to remove the {serverName} server?'
|
||||
values={{serverName}}
|
||||
/>
|
||||
</p>
|
||||
<FormattedMessage
|
||||
id='renderer.components.removeServerModal.body'
|
||||
defaultMessage='This will remove the server from your Desktop App but will not delete any of its data - you can add the server back at any time.'
|
||||
/>
|
||||
<br/><br/>
|
||||
<FormattedMessage
|
||||
id='renderer.components.removeServerModal.confirm'
|
||||
defaultMessage='Are you sure you wish to remove the server?'
|
||||
/>
|
||||
</Modal.Body>
|
||||
)}
|
||||
/>
|
||||
|
@@ -3,9 +3,11 @@
|
||||
|
||||
import type {Certificate} from 'electron/renderer';
|
||||
import React, {Fragment} from 'react';
|
||||
import {Modal, Button, Row, Col} from 'react-bootstrap';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {Modal} from 'renderer/components/Modal';
|
||||
import IntlProvider from 'renderer/intl_provider';
|
||||
|
||||
type Props = {
|
||||
certificate: Certificate;
|
||||
onOk: () => void;
|
||||
@@ -32,7 +34,7 @@ export default class ShowCertificateModal extends React.PureComponent<Props, Sta
|
||||
const certificateSection = (descriptor: React.ReactNode) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<dt className={'certificate-key'}>{descriptor}</dt>
|
||||
<dt className={'certificate-key'}><strong>{descriptor}</strong></dt>
|
||||
<dd className={'certificate-section'}><span/></dd>
|
||||
</Fragment>
|
||||
);
|
||||
@@ -41,29 +43,12 @@ export default class ShowCertificateModal extends React.PureComponent<Props, Sta
|
||||
const val = value ? `${value}` : <span/>;
|
||||
return (
|
||||
<Fragment>
|
||||
<dt className={'certificate-key'}>{descriptor}</dt>
|
||||
<dt className={'certificate-key'}><strong>{descriptor}</strong></dt>
|
||||
<dd className={'certificate-value'}>{val}</dd>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
if (this.state.certificate === null) {
|
||||
return (
|
||||
<Modal
|
||||
bsClass='modal'
|
||||
className='show-certificate'
|
||||
onHide={() => {}}
|
||||
>
|
||||
<Modal.Body>
|
||||
<FormattedMessage
|
||||
id='renderer.components.showCertificateModal.noCertSelected'
|
||||
defaultMessage='No certificate Selected'
|
||||
/>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const utcSeconds = (date: number) => {
|
||||
const d = new Date(0);
|
||||
d.setUTCSeconds(date);
|
||||
@@ -74,18 +59,27 @@ export default class ShowCertificateModal extends React.PureComponent<Props, Sta
|
||||
const creation = utcSeconds(this.state.certificate?.validStart || 0);
|
||||
const dateDisplayOptions = {dateStyle: 'full' as const, timeStyle: 'full' as const};
|
||||
const dateLocale = 'en-US'; // TODO: Translate?
|
||||
|
||||
return (
|
||||
<Modal
|
||||
bsClass='modal'
|
||||
className='show-certificate'
|
||||
show={this.state.certificate !== null}
|
||||
onHide={() => {}}
|
||||
>
|
||||
<Modal.Header className={'no-border'}>
|
||||
<Modal.Title>{'Certificate information'}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p className='details'>{'Details'}</p>
|
||||
<IntlProvider>
|
||||
<Modal
|
||||
id='showCertificateModal'
|
||||
show={this.state.certificate !== null}
|
||||
onExited={this.handleOk}
|
||||
modalHeaderText={
|
||||
<FormattedMessage
|
||||
id='renderer.components.showCertificateModal.title'
|
||||
defaultMessage='Certificate information'
|
||||
/>
|
||||
}
|
||||
confirmButtonText={
|
||||
<FormattedMessage
|
||||
id='label.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
}
|
||||
handleConfirm={this.handleOk}
|
||||
>
|
||||
<dl>
|
||||
{certificateSection(
|
||||
<FormattedMessage
|
||||
@@ -154,26 +148,8 @@ export default class ShowCertificateModal extends React.PureComponent<Props, Sta
|
||||
this.state.certificate?.fingerprint.split('/')[0],
|
||||
)}
|
||||
</dl>
|
||||
</Modal.Body>
|
||||
<Modal.Footer className={'no-border'}>
|
||||
<div className='container-fluid'>
|
||||
<Row>
|
||||
<Col>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={this.handleOk}
|
||||
className={'primary'}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='label.close'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</Modal>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user