[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:
Devin Binnie
2025-02-18 10:58:28 -05:00
committed by GitHub
parent 34963cbaa8
commit 4d754efdd7
44 changed files with 1854 additions and 927 deletions

View File

@@ -204,11 +204,11 @@ export class ServerViewState {
return;
}
const modalPromise = ModalManager.addModal<string, boolean>(
const modalPromise = ModalManager.addModal<null, boolean>(
'removeServer',
'mattermost-desktop://renderer/removeServer.html',
getLocalPreload('internalAPI.js'),
server.name,
null,
mainWindow,
);

View File

@@ -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>
);
}

View 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>
</>
);
};

View File

@@ -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>
);
}

View File

@@ -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>
)}
/>

View File

@@ -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>
);
}
}

View File

@@ -0,0 +1,358 @@
@use "variables";
.style--none {
padding: 0;
border: none;
background: transparent;
&:focus {
outline: 0;
text-decoration: none;
}
&.btn--block {
width: 100%;
text-align: left;
}
&:hover,
&:active {
text-decoration: none;
}
}
.rounded-button {
border-radius: 50%;
}
button {
.unread-badge {
display: inline-block;
width: 8px;
height: 8px;
border-radius: var(--radius-full);
margin: 0 0 0 40px;
background: #f74343;
}
}
.btn {
// Important is given to override bootstrap styles on different states
display: inline-flex;
height: 40px;
align-items: center;
justify-content: center;
padding: 0 20px;
border: none;
border-radius: var(--radius-s);
font-size: 14px;
font-weight: 600;
gap: 8px;
outline: none;
transition: all 0.15s ease;
&.btn-icon {
width: 40px;
min-width: 40px;
padding: 0;
background-color: transparent;
color: rgba(var(--center-channel-color-rgb), var(--icon-opacity));
&:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), var(--icon-opacity-hover));
}
&:active {
background-color: rgba(var(--button-bg-rgb), 0.08);
color: rgba(var(--button-bg-rgb), 1);
}
i {
font-size: 24px;
}
&.btn-xs {
width: 24px;
min-width: 24px;
height: 24px;
padding: 0;
font-size: 12px;
gap: 4px;
&.btn-compact {
width: 20px;
height: 20px;
}
i {
font-size: 14.4px;
}
}
&.btn-sm {
width: 32px;
min-width: 32px;
height: 32px;
padding: 0;
font-size: 14px;
gap: 4px;
&.btn-compact {
width: 28px;
height: 28px;
}
i {
font-size: 18px;
}
}
&.btn-lg {
width: 48px;
height: 48px;
padding: 0;
&.btn-compact {
width: 36px;
height: 36px;
}
i {
font-size: 31.2px;
}
}
}
&:not(.a11y--active) {
box-shadow: none;
}
&:active {
box-shadow: none;
}
& + .btn {
margin-left: 8px;
}
&.btn-full {
width: 100%;
}
i {
display: inline-flex;
width: 16px;
height: 16px;
align-items: center;
justify-content: center;
margin-right: 0;
font-size: 18px;
}
&.btn-xs {
height: 24px;
padding: 0 10px;
font-size: 11px;
gap: 6px;
i {
width: 12px;
height: 12px;
font-size: 14.4px;
}
}
&.btn-sm {
height: 32px;
padding: 0 16px;
font-size: 12px;
gap: 6px;
i {
width: 12px;
height: 12px;
font-size: 14.4px;
}
}
&.btn-lg {
height: 48px;
padding: 0 24px;
font-size: 16px;
gap: 10px;
i {
width: 20px;
height: 20px;
font-size: 24px;
}
}
&.btn-link {
padding: 0;
border: none;
background: transparent;
color: rgba(var(--button-bg-rgb), 1);
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
&.btn-primary {
position: relative;
border-color: transparent;
background-color: rgb(var(--button-bg-rgb));
color: rgb(var(--button-color-rgb));
// These hover and active values are for things outside the app__body, the correct theme styles for the primary button are applied in utils.jsx
&:hover {
background-color: #1a51c8;
}
&:active,
&:focus {
background-color: #184ab6;
}
&:disabled,
&:disabled:hover,
&:disabled:active {
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.32);
opacity: 1;
}
}
&.btn-secondary {
border: 1px solid rgb(var(--button-bg-rgb));
background: transparent;
color: rgb(var(--button-bg-rgb));
&.btn-danger {
border-color: currentColor;
background: transparent;
color: var(--error-text);
&:hover {
border-color: currentColor;
background-color: rgba(var(--error-text-color-rgb), 0.08);
color: var(--error-text);
}
&:active,
&:focus {
border-color: currentColor;
background-color: rgba(var(--error-text-color-rgb), 0.16);
color: var(--error-text);
}
}
&:hover {
background-color: rgb(var(--button-bg-rgb), 0.08);
}
&:active {
background-color: rgb(var(--button-bg-rgb), 0.16);
}
}
&.btn-tertiary {
background: rgba(var(--button-bg-rgb), 0.08);
color: rgb(var(--button-bg-rgb));
&:hover {
background-color: rgb(var(--button-bg-rgb), 0.12);
}
&:active {
background-color: rgb(var(--button-bg-rgb), 0.16);
outline: none;
}
&:disabled,
&:disabled:hover,
&:disabled:active {
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.32);
opacity: 1;
}
&.btn-danger {
background-color: rgba(var(--error-text-color-rgb), 0.08);
color: var(--error-text);
&:hover {
background-color: rgba(var(--error-text-color-rgb), 0.12);
color: var(--error-text);
}
&:active,
&:focus {
background-color: rgba(var(--error-text-color-rgb), 0.16);
color: var(--error-text);
}
}
}
&.btn-quaternary {
background: transparent;
color: rgb(var(--button-bg-rgb));
&:hover {
background: rgba(var(--button-bg-rgb), 0.08);
}
&:active {
background-color: rgb(var(--button-bg-rgb), 0.12);
}
}
&.btn-danger {
background: var(--error-text);
color: variables.$white;
.app__body & {
color: variables.$white;
&:hover,
&:focus,
&:active {
color: variables.$white;
}
}
&:hover,
&:focus,
&:active {
color: variables.$white;
}
}
&.btn-transparent {
padding: 7px 12px;
border: none;
background: transparent;
}
&.btn-inactive {
border-color: transparent;
background: variables.$light-gray;
color: variables.$white;
}
.fa {
margin-right: 3px;
&.margin-right {
margin-right: 6px;
}
&.margin-left {
margin-left: 6px;
}
}
}

View File

@@ -17,6 +17,19 @@
--elevation-5: 0 12px 32px 0 rgba(0, 0, 0, 0.12);
--elevation-6: 0 20px 32px 0 rgba(0, 0, 0, 0.12);
/* Corner Radius variables */
--radius-xs: 2px;
--radius-s: 4px;
--radius-m: 8px;
--radius-l: 12px;
--radius-xl: 16px;
--radius-full: 50%;
/* Border variables */
--border-default: solid 1px rgba(var(--center-channel-color-rgb), 0.12);
--border-light: solid 1px rgba(var(--center-channel-color-rgb), 0.08);
--border-dark: solid 1px rgba(var(--center-channel-color-rgb), 0.16);
/* Denim - used for light mode */
--away-indicator-rgb: 255, 188, 31;
--button-bg-rgb: 28, 88, 217;
@@ -69,7 +82,7 @@
--mention-highlight-link: #1b1d22;
}
.LoadingScreen--darkMode, .ErrorView.darkMode {
.darkMode .Modal, .LoadingScreen--darkMode, .ErrorView.darkMode {
/* Onyx - used for dark mode*/
--away-indicator-rgb: 245, 171, 0;
--button-bg-rgb: 74, 124, 232;

View File

@@ -79,3 +79,15 @@
transform: translate3d(4px, 0, 0);
}
}
@mixin font-smoothing($value: antialiased) {
@if $value == antialiased {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@else {
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: auto;
}
}

View File

@@ -0,0 +1,148 @@
@use "mixins";
@use "variables";
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: url('../../assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2') format('woff2');
}
@font-face {
font-family: 'Metropolis';
font-style: normal;
font-weight: 600;
src: url('../../../assets/fonts/Metropolis-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'Metropolis';
font-style: italic;
font-weight: 600;
src: url('../../../assets/fonts/Metropolis-SemiBoldItalic.woff') format('woff');
}
@font-face {
font-family: 'Metropolis';
font-style: normal;
font-weight: 400;
src: url('../../../assets/fonts/Metropolis-Regular.woff') format('woff');
}
@font-face {
font-family: 'Metropolis';
font-style: italic;
font-weight: 400;
src: url('../../../assets/fonts/Metropolis-RegularItalic.woff') format('woff');
}
@font-face {
font-family: 'Metropolis';
font-style: normal;
font-weight: 300;
src: url('../../../assets/fonts/Metropolis-Light.woff') format('woff');
}
@font-face {
font-family: 'Metropolis';
font-style: italic;
font-weight: 300;
src: url('../../../assets/fonts/Metropolis-LightItalic.woff') format('woff');
}
@font-face {
font-family: 'FontAwesome';
font-style: normal;
font-weight: 300;
src: url('../../../assets/fonts/fontawesome-webfont.woff') format('woff');
}
b,
strong {
font-weight: 600;
}
a {
color: variables.$primary-color;
cursor: pointer;
text-decoration: none;
word-break: break-word;
&:focus,
&:hover {
color: variables.$primary-color--hover;
}
}
body {
@include mixins.font-smoothing;
font-family: 'Open Sans', sans-serif;
}
h1,
h2,
h3 {
font-family: Metropolis, sans-serif;
}
h1 {
font-weight: 600;
}
label {
&.has-error {
color: variables.$red;
font-weight: normal;
}
}
.small {
font-size: 12px;
}
.light {
opacity: 0.73;
}
ul,
ol {
padding-left: 22px;
margin-top: 3px;
margin-bottom: 0.11em;
}

View File

@@ -1,2 +1,11 @@
@use 'sass:color';
// Since they can be used on any modal, menu or popover for now they are highest
$z-index-tooltip: 1350;
$z-index-tooltip: 1350;
// Color Variables
$primary-color: #166de0;
$primary-color--hover: color.adjust($primary-color, $lightness: -10%);
$white: rgb(255, 255, 255);
$light-gray: rgba(0, 0, 0, 0.15);
$red: rgb(214, 73, 70);

View File

@@ -119,7 +119,7 @@
&:disabled {
background: none;
color: rgba(var(--center-channel-text-rgb), 0.32);
color: rgba(var(--center-channel-color-rgb), 0.32);
cursor: not-allowed;
}

View File

@@ -1,210 +0,0 @@
.certificate-modal .modal,
.show-certificate .modal {
background-color: aliceblue;
margin-top: 40px;
}
.show-certificate .modal-dialog {
width: 800px;
}
.modal-header {
border-bottom: 0px;
}
.modal-header::after {
border-bottom: solid 1px #E5E5E5;
width: 100%;
transform: translateY(15px);
}
.modal-footer::before {
border-top: solid 1px #E5E5E5;
width: 100%;
transform: translateY(-15px);
}
.modal-body :last-child {
margin-bottom: 0;
}
.certificate-modal .col-sm-4 {
padding: 0px;
text-align: left;
}
.certificate-modal .col-sm-8 {
padding: 0px;
}
.certificate-list thead {
width: 557.89px;
}
.certificate-list thead>tr {
padding: 0px 5px;
}
.certificate-list thead>tr>th {
font-family: Helvetica Neue;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
padding: 3px 5px;
border-bottom: 1px solid #CCCCCC;
color: #333333;
height: 22px;
}
.certificate-list thead tr th:first-child span {
padding-left: 5px;
}
.certificate-list thead tr th span {
border-right: solid 1px #E5E5E5;
display: block;
}
.certificate-list thead tr th:last-child span {
border-right: none;
}
.certificate-list tbody tr {
user-select: none;
cursor: pointer;
color: #555555;
}
.certificate-list tbody>tr>td {
max-width: 165px;
height: 47px;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 17px;
padding: 15px 10px;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.certificate-list tbody tr td:first-child {
padding-left: 15px;
max-width: 227px;
}
.certificate-list tbody tr.selected {
background: #457AB2;
color: #FFFFFF;
}
table.certificate-list {
background: #FFFFFF;
border: 1px solid #CCCCCC;
border-radius: 4px;
border-collapse: unset;
box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.0008);
height: 150px;
}
table.certificate-list:focus {
border: 1px solid #66AFE9;
box-sizing: border-box;
box-shadow: 0px 0px 8px rgba(102, 175, 233, 0.6), inset 1px 1px 0px rgba(0, 0, 0, 0.00075);
}
.show-certificate button,
.certificate-modal button {
background: #FFFFFF;
border: 1px solid #CCCCCC;
box-sizing: border-box;
border-radius: 4px;
padding: 9px 12px;
line-height: 16px;
}
.show-certificate button:disabled,
.certificate-modal button:disabled {
opacity: 0.5;
}
.show-certificate button.primary,
.certificate-modal button.primary {
background: #457AB2;
color: #FFFFFF;
border: 1px solid #2E6DA4;
}
.show-certificate button.primary:hover,
.certificate-modal button.primary:hover {
background: #659AD2;
}
.certificate-modal button.info {
color: #457AB2;
}
.certificate-modal button.info:disabled {
color: #000;
}
.show-certificate .subtitle,
.certificate-modal .subtitle {
color: #737373;
margin: 0px 0px 15px 0px;
}
.show-certificate .no-border,
.certificate-modal .no-border {
border: none;
}
.show-certificate dl {
overflow-y: auto;
}
.show-certificate dt, dd {
float: left;
margin: 5px;
}
.show-certificate dt {
width: 150px;
clear:both
}
.show-certificate dd {
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
}
.certificate-key {
font-style: normal;
font-weight: bold;
font-size: 12px;
line-height: 15px;
color: #333333;
text-align: right;
}
.certificate-value {
padding-top: 1px;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 14px;
text-align: left;
color: #333333;
}
.certificate-section {
border-bottom: 1px solid #E5E5E5;
width: 598px;
height: 8px;
}
.show-certificate .details {
margin: 15px;
font-weight: bold;
font-style: normal;
font-size: 12px;
line-height: 15px;
color: #333333;
}

View File

@@ -0,0 +1,50 @@
@use "../css_variables";
.Modal .CertificateModal {
width: 832px;
max-width: 832px;
}
.CertificateModal_certInfoButton {
margin-right: auto
}
.CertificateModal_list {
text-align: left;
border: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
border-radius: var(--radius-s);
border-spacing: 0;
width: 100%;
th, td {
padding: 10px 12px;
}
th {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
& + th {
border-left: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
}
}
td {
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
& + td {
border-left: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
}
}
tbody tr {
cursor: pointer;
&:hover {
background-color: rgba(var(--center-channel-color-rgb), 0.16);
}
&.selected {
background-color: rgba(var(--button-bg-rgb), 0.12);
}
}
}

View File

@@ -8,7 +8,7 @@
font-family: 'Open Sans';
.alternate-link__message {
color: var(--center-channel-text);
color: var(--center-channel-color);
font-size: 12px;
font-weight: 400;
line-height: 16px;
@@ -117,7 +117,7 @@
.ConfigureServer__card {
width: 540px;
box-sizing: border-box;
border: 1px solid rgba(var(--center-channel-text-rgb), 0.08);
border: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
margin-left: 60px;
background-color: var(--center-channel-bg);
border-radius: 8px;
@@ -143,6 +143,7 @@
font-weight: 600;
line-height: 28px;
margin-bottom: 32px;
margin-top: 0;
}
.ConfigureServer__card-form {

View File

@@ -6,6 +6,7 @@
width: 100%;
min-height: 100px;
padding: 0 40px;
box-sizing: border-box;
.Header__main {
display: flex;

View File

@@ -34,6 +34,7 @@
font-size: 14px;
line-height: 20px;
outline: 0;
font-family: 'Open Sans', sans-serif;
&::placeholder {
color: rgba(var(--center-channel-color-rgb), 0.64);

View File

@@ -1,5 +1,6 @@
body {
background-color: transparent;
margin: 0;
}
.LoadingScreen {

View File

@@ -0,0 +1,464 @@
@use '../css_variables';
@use "../buttons";
@use "../typography";
@import '~@mattermost/compass-icons/css/compass-icons.css';
body {
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
button {
font-family: inherit;
&:not(:disabled) {
cursor: pointer;
}
}
background-color: transparent;
}
.Modal {
position: fixed;
top: 0;
left: 0;
z-index: 1050;
height: 100%;
width: 100%;
color: var(--center-channel-color);
outline: 0;
display: flex;
align-items: center;
.Modal_body {
overflow: auto;
max-height: calc(90vh - 80px);
padding: 2px 32px;
font-size: 14px;
&.overflow--visible {
overflow: visible;
}
&.divider {
border-top: var(--border-light);
}
.form-control {
height: 40px;
box-sizing: border-box;
border: var(--border-default);
border-radius: 4px;
&:focus {
border-color: var(--button-bg);
}
&.has-error {
border-color: var(--error-text);
}
}
.input-wrapper {
position: relative;
}
.input-clear {
top: 12px;
right: 12px;
width: 16px;
height: 16px;
color: var(--center-channel-color);
font-size: 18px;
font-style: normal;
font-weight: normal;
line-height: 16px;
opacity: 0.48;
}
}
.Modal_footer {
display: flex;
flex-wrap: wrap;
justify-content: end;
padding: 24px 32px;
border: none;
grid-row-gap: 8px;
&.divider {
border-top: var(--border-light);
}
&.Modal_footer--invisible {
overflow: hidden;
height: 0;
padding: 0;
}
.error-text {
margin: 10px;
float: left;
font-weight: bold;
text-align: left;
}
& .btn + .btn {
margin-left: 8px;
}
}
textarea {
overflow-x: hidden;
}
.custom-textarea {
padding: 12px 30px 12px 15px;
}
.info__label {
margin-bottom: 3px;
font-size: 0.9em;
font-weight: 600;
opacity: 0.75;
}
.info__value {
padding-left: 10px;
word-break: break-word;
p {
white-space: pre-wrap;
}
}
.Modal_dialog {
max-width: 95%;
margin-right: auto;
margin-bottom: 0;
margin-left: auto;
position: relative;
width: auto;
max-width: 500px;
&.Modal_xl {
width: 800px;
}
}
.Modal_push-down {
margin-top: 60px;
}
.Modal_next-bar {
position: absolute;
top: 0;
right: 0;
height: 100%;
}
.Modal_header {
display: flex;
min-height: 76px;
align-items: center;
justify-content: space-between;
padding: 16px 64px 16px 32px;
border: none;
background: transparent;
box-sizing: border-box;
button.close {
border: 0;
background-color: transparent;
float: right;
line-height: 1;
text-transform: none;
overflow: visible;
padding: 0;
margin: 0;
margin-top: -2px;
.sr-only {
position: absolute;
clip: rect(0,0,0,0);
}
}
&.divider {
border-bottom: var(--border-light);
}
&::before,
&::after {
content: none;
}
.Modal_header-back-button {
margin-left: -12px;
}
.btn {
position: relative;
top: -2px;
}
.close {
position: absolute;
top: 18px;
right: 18px;
width: 40px;
height: 40px;
border-radius: var(--radius-s);
color: rgba(var(--center-channel-color-rgb), 0.64);
font-size: 30px;
font-weight: 400;
opacity: 1;
text-shadow: none;
&.icon-close {
font-size: 24px;
}
&:hover {
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.8);
}
&:active {
background: rgba(var(--button-bg-rgb), 0.08);
color: var(--button-bg);
}
.Modal_title {
width: 100%;
background: transparent;
color: var(--center-channel-color);
font-size: 22px;
line-height: 28px;
}
}
.Modal_title {
width: 100%;
background: transparent;
color: var(--center-channel-color);
font-size: 22px;
line-height: 28px;
word-break: break-word;
}
}
.Modal_content {
border: var(--border-default);
border-radius: var(--radius-l);
background: var(--center-channel-bg);
}
.Modal_chevron-icon {
top: 50%;
font-size: 120%;
}
.Modal_prev-bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
.Modal_overflow {
.Modal_body {
overflow: visible;
}
}
.no-header__img {
margin-top: -40px;
}
.Modal_header {
.Modal__header__text_container {
margin-top: 8px;
width: 100%;
.Modal_subheading-container {
color: rgba(var(--center-channel-color-rgb), 0.75);
}
}
p#Modal_subHeading {
font-size: 12px;
margin-block: 10px;
}
}
.Modal_body {
max-height: 100%;
padding: 0;
&.divider {
border-top: var(--border-light);
}
.form-control {
height: 40px;
box-sizing: border-box;
border: var(--border-default);
border-radius: 4px;
&:focus {
border-color: var(--button-bg);
}
&.has-error {
border-color: var(--error-text);
}
}
.MaxLengthInput {
&.form-control.has-error {
padding-right: 66px;
}
&__validation {
position: absolute;
top: 12px;
right: 56px;
color: var(--error-text);
font-size: 14px;
font-style: normal;
font-weight: normal;
line-height: 16px;
}
}
.input-wrapper {
position: relative;
}
.input-clear {
top: 12px;
right: 12px;
width: 16px;
height: 16px;
color: var(--center-channel-color);
font-size: 18px;
font-style: normal;
font-weight: normal;
line-height: 16px;
opacity: 0.48;
}
}
&__header h1 {
margin-top: 0;
margin-bottom: 0;
}
&__compassDesign {
.Modal_content {
.Modal_body {
.Modal__body {
padding: 0;
&.padding {
padding: 0px 32px;
}
}
}
.Modal_error {
display: flex;
box-sizing: border-box;
flex: 1;
padding: 14px 32px;
border: 1px solid rgba(var(--dnd-indicator-rgb), 0.16);
margin-bottom: 24px;
background: rgba(var(--dnd-indicator-rgb), 0.08);
border-radius: var(--radius-s);
span {
color: var(--center-channel-color);
font-size: 14px;
font-weight: 600;
line-height: 24px;
}
i {
margin-right: 8px;
color: var(--error-text);
font-size: 24px;
line-height: 24px;
&::before {
margin: 0;
}
}
}
}
@media screen and (max-width: 640px) {
margin: 0;
.Modal_header {
box-shadow: var(--elevation-2);
}
}
}
&.fade {
.Modal_dialog {
transition: transform .3s ease-out,-webkit-transform .3s ease-out;
transform: translate(0, -50px);
}
}
&.show .Modal_dialog {
transform: none;
}
.Modal_body.divider .Input_container {
margin-top: 24px;
}
.Input_container + .Input_container {
margin-top: 24px;
}
}
.fade {
transition: opacity 0.15s linear;
&:not(.show) {
opacity: 0;
}
}
.btn.btn-primary.btn-danger:hover {
background: linear-gradient(0deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08)), var(--error-text);
}
.Modal_backdrop {
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
z-index: 1040;
width: 100vw;
height: 100vh;
background-color: #000;
&.fade {
opacity: 0;
}
&.show {
opacity: 0.5;
}
&.in {
opacity: 0.64;
}
}

View File

@@ -1,38 +1,34 @@
.NewServerModal-noBottomSpace {
padding-bottom: 0px;
margin-bottom: 0px;
}
@use "../css_variables";
.NewServerModal-validation {
margin-top: 8px;
margin-right: auto;
.Modal .NewServerModal {
width: 600px;
max-width: 600px;
hr {
margin-top: 24px;
border: 0;
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
}
.Modal_body {
margin-bottom: 12px;
}
}
.NewServerModal__toggle__description {
display: block;
margin-top: 0.25rem;
font-weight: 400;
font-size: 12px;
display: flex;
flex-direction: column;
> div {
margin-top: 4px;
> span {
margin-left: 4px;
}
}
.error {
color: #d24b4e;
}
.warning {
color: #c79e3f;
}
.success {
color: #06d6a0;
}
line-height: 16px;
max-width: 400px;
}
.NewServerModal-validationSpinner {
width: 0.75rem;
height: 0.75rem;
margin-left: 2px;
margin-right: 4px;
.NewServerModal__permissions__title {
margin-top: 24px;
margin-bottom: 12px;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}

View File

@@ -4,10 +4,11 @@
display: flex;
column-gap: 12px;
font-weight: inherit;
line-height: 16px;
line-height: 24px;
cursor: pointer;
margin-bottom: 0;
padding: 10px 0;
font-size: 16px;
&.disabled {
cursor: default;
@@ -63,6 +64,10 @@
&.disabled {
background-color: var(--button-bg-30);
}
}
}
}
i {
font-size: 20px;
}
}

View File

@@ -23,7 +23,7 @@
}
.WelcomeScreenSlide__subtitle {
color: rgba(var(--center-channel-text-rgb), 0.72);
color: rgba(var(--center-channel-color-rgb), 0.72);
font-family: Open Sans;
font-size: 16px;
font-weight: 400;

View File

@@ -4,6 +4,5 @@
@import url("PermissionRequestDialog.css");
@import url("TabBar.css");
@import url("UpdaterPage.css");
@import url("CertificateModal.css");
@import url("LoadingScreen.css");
@import url("LoadingAnimation.css");

View File

@@ -4,6 +4,18 @@
@include meta.load-css('bootstrap-dark/src/bootstrap-dark.css');
color: #fff;
> div.modal {
color: #fff;
.modal-header .modal-title, .modal-header .close {
color: #fff;
}
.modal-content {
background-color: #191B1F;
}
}
.SettingsPage__spellCheckerLocalesDropdown .SettingsPage__spellCheckerLocalesDropdown__control {
background: #242a30;
}
@@ -34,5 +46,52 @@
.Toggle___switch.disabled {
background: rgba(var(--center-channel-bg-rgb), 0.08);
}
.Input {
color: var(--button-color);
background-color: unset;
&::placeholder {
color: rgba(var(--button-color-rgb), 0.56);
}
}
.Input_wrapper {
color: rgba(var(--button-color-rgb), 0.56);
}
.Input___info {
color: rgba(var(--button-color-rgb), 0.56);
}
.Input_fieldset {
background-color: #191B1F;
border: 1px solid rgba(#fff, 0.16);
&:hover {
border-color: rgba(#fff, 0.48);
}
&:focus-within {
border-color: #fff;
box-shadow: inset 0 0 0 1px #fff;
color: var(--button-color);
.Input_legend {
color: var(--button-color);
}
}
}
.Input_legend {
background-color: #191B1F;
color: rgba(var(--button-color-rgb), 0.64);
}
&.disabled {
.Input_fieldset {
background: rgba(var(--button-color-rgb), 0.08);
}
}
}

View File

@@ -5,10 +5,6 @@ import type {Certificate} from 'electron/renderer';
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import 'renderer/css/components/CertificateModal.css';
import type {CertificateModalInfo} from 'types/modals';
import SelectCertificateModal from './certificateModal';

View File

@@ -1,18 +1,21 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import type {Certificate} from 'electron/renderer';
import React, {Fragment} from 'react';
import {Modal, Button, Table, Row, Col} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {Modal} from 'renderer/components/Modal';
import IntlProvider from 'renderer/intl_provider';
import ShowCertificateModal from '../../components/showCertificateModal';
import 'renderer/css/components/CertificateModal.scss';
type Props = {
onSelect: (cert: Certificate) => void;
onCancel?: () => void;
onCancel: () => void;
getCertInfo: () => Promise<{url: string; list: Certificate[]}>;
}
@@ -117,107 +120,99 @@ export default class SelectCertificateModal extends React.PureComponent<Props, S
);
}
const footerContent = (
<>
<button
type='button'
disabled={this.state.selectedIndex === undefined}
onClick={this.handleCertificateInfo}
className={classNames('Modal__button btn btn-tertiary CertificateModal_certInfoButton', {
disabled: this.state.selectedIndex === undefined,
})}
>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.certInfoButton'
defaultMessage='Certificate Information'
/>
</button>
<button
type='button'
className={classNames('Modal__button btn btn-tertiary')}
onClick={this.props.onCancel}
>
<FormattedMessage
id='modal.cancel'
defaultMessage='Cancel'
/>
</button>
<button
type='submit'
className={classNames('Modal__button btn btn-primary confirm', {
disabled: this.state.selectedIndex === undefined,
})}
onClick={this.handleOk}
disabled={this.state.selectedIndex === undefined}
>
<FormattedMessage
id='modal.confirm'
defaultMessage='Confirm'
/>
</button>
</>
);
return (
<IntlProvider>
<Modal
bsClass='modal'
className='certificate-modal'
id='selectCertificateModal'
className='CertificateModal'
show={Boolean(this.state.list && this.state.url)}
onHide={() => {}}
onExited={this.props.onCancel}
modalHeaderText={
<FormattedMessage
id='renderer.modals.certificate.certificateModal.title'
defaultMessage='Select a certificate'
/>
}
modalSubheaderText={
<FormattedMessage
id='renderer.modals.certificate.certificateModal.subtitle'
defaultMessage='Select a certificate to authenticate yourself to {url}'
values={{url: this.state.url}}
/>
}
footerContent={footerContent}
>
<Modal.Header>
<Modal.Title>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.title'
defaultMessage='Select a certificate'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className={'subtitle'}>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.subtitle'
defaultMessage='Select a certificate to authenticate yourself to {url}'
values={{url: this.state.url}}
/>
</p>
<Table
striped={true}
hover={true}
responsive={true}
className='certificate-list'
tabIndex={1}
>
<thead>
<tr>
<th><span className={'divider'}>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.subject'
defaultMessage='Subject'
/>
</span></th>
<th><span className={'divider'}>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.issuer'
defaultMessage='Issuer'
/>
</span></th>
<th>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.serial'
defaultMessage='Serial'
/>
</th>
</tr>
</thead>
<tbody>
{this.renderCerts(this.state.list!)}
<tr/* this is to correct table height without affecting real rows *//>
</tbody>
</Table>
</Modal.Body>
<Modal.Footer className={'no-border'}>
<div className={'container-fluid'}>
<Row>
<Col sm={4}>
<Button
variant='info'
disabled={this.state.selectedIndex === null}
onClick={this.handleCertificateInfo}
className={'info'}
>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.certInfoButton'
defaultMessage='Certificate Information'
/>
</Button>
</Col>
<Col sm={8}>
<Button
variant='link'
onClick={this.props.onCancel}
className={'secondary'}
>
<FormattedMessage
id='label.cancel'
defaultMessage='Cancel'
/>
</Button>
<Button
variant='primary'
onClick={this.handleOk}
disabled={this.state.selectedIndex === null}
className={'primary'}
>
<FormattedMessage
id='label.ok'
defaultMessage='OK'
/>
</Button>
</Col>
</Row>
</div>
</Modal.Footer>
<table
className='CertificateModal_list'
tabIndex={1}
>
<thead>
<tr>
<th><span className={'divider'}>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.subject'
defaultMessage='Subject'
/>
</span></th>
<th><span className={'divider'}>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.issuer'
defaultMessage='Issuer'
/>
</span></th>
<th>
<FormattedMessage
id='renderer.modals.certificate.certificateModal.serial'
defaultMessage='Serial'
/>
</th>
</tr>
</thead>
<tbody>
{this.renderCerts(this.state.list!)}
</tbody>
</table>
</Modal>
</IntlProvider>
);

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import 'renderer/css/modals-dark.scss';
export default function addDarkModeListener() {
const setDarkMode = (darkMode: boolean) => {
if (darkMode) {

View File

@@ -1,9 +1,6 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import React, {useEffect, useState} from 'react';
import ReactDOM from 'react-dom';

View File

@@ -6,8 +6,6 @@ import ReactDOM from 'react-dom';
import LoadingScreen from '../../components/LoadingScreen';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import 'renderer/css/components/LoadingAnimation.css';
import 'renderer/css/components/LoadingScreen.css';

View File

@@ -9,9 +9,6 @@ import IntlProvider from 'renderer/intl_provider';
import type {LoginModalInfo} from 'types/modals';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import LoginModal from './loginModal';
import setupDarkMode from '../darkMode';

View File

@@ -4,11 +4,12 @@
import type {AuthenticationResponseDetails, AuthInfo} from 'electron/renderer';
import React from 'react';
import {Button, Col, FormLabel, Form, FormGroup, FormControl, Modal} from 'react-bootstrap';
import type {IntlShape} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import {parseURL} from 'common/utils/url';
import Input, {SIZE} from 'renderer/components/Input';
import {Modal} from 'renderer/components/Modal';
import type {LoginModalInfo} from 'types/modals';
@@ -44,8 +45,7 @@ class LoginModal extends React.PureComponent<Props, State> {
this.setState({request, authInfo});
};
handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
handleSubmit = () => {
this.props.onLogin(this.state.request!, this.state.username, this.state.password);
this.setState({
username: '',
@@ -55,8 +55,7 @@ class LoginModal extends React.PureComponent<Props, State> {
});
};
handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
handleCancel = () => {
this.props.onCancel(this.state.request!);
this.setState({
username: '',
@@ -101,94 +100,44 @@ class LoginModal extends React.PureComponent<Props, State> {
return (
<Modal
id='loginModal'
show={Boolean(this.state.request && this.state.authInfo)}
onExited={this.handleCancel}
modalHeaderText={
<FormattedMessage
id='renderer.modals.login.loginModal.title'
defaultMessage='Authentication Required'
/>
}
handleConfirm={this.handleSubmit}
confirmButtonText={
<FormattedMessage
id='label.login'
defaultMessage='Login'
/>
}
handleCancel={this.handleCancel}
modalSubheaderText={this.renderLoginModalMessage()}
>
<Modal.Header>
<Modal.Title>
<FormattedMessage
id='renderer.modals.login.loginModal.title'
defaultMessage='Authentication Required'
/>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
{this.renderLoginModalMessage()}
</p>
<Form
onSubmit={this.handleSubmit}
>
<FormGroup>
<Col
as={FormLabel}
sm={2}
>
<FormattedMessage
id='renderer.modals.login.loginModal.username'
defaultMessage='User Name'
/>
</Col>
<Col sm={10}>
<FormControl
type='text'
placeholder={intl.formatMessage({id: 'renderer.modals.login.loginModal.username', defaultMessage: 'User Name'})}
onChange={this.setUsername}
value={this.state.username}
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
e.stopPropagation();
}}
/>
</Col>
</FormGroup>
<FormGroup>
<Col
as={FormLabel}
sm={2}
>
<FormattedMessage
id='renderer.modals.login.loginModal.password'
defaultMessage='Password'
/>
</Col>
<Col sm={10}>
<FormControl
type='password'
placeholder={intl.formatMessage({id: 'renderer.modals.login.loginModal.password', defaultMessage: 'Password'})}
onChange={this.setPassword}
value={this.state.password}
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
e.stopPropagation();
}}
/>
</Col>
</FormGroup>
<FormGroup>
<Col sm={12}>
<div className='pull-right'>
<Button
type='submit'
variant='primary'
>
<FormattedMessage
id='label.login'
defaultMessage='Login'
/>
</Button>
{ ' ' }
<Button
variant='link'
onClick={this.handleCancel}
>
<FormattedMessage
id='label.cancel'
defaultMessage='Cancel'
/>
</Button>
</div>
</Col>
</FormGroup>
</Form>
</Modal.Body>
<Input
autoFocus={true}
id='loginModalUsername'
name='username'
type='text'
inputSize={SIZE.LARGE}
value={this.state.username}
onChange={this.setUsername}
placeholder={intl.formatMessage({id: 'renderer.modals.login.loginModal.username', defaultMessage: 'User Name'})}
/>
<Input
id='loginModalPassword'
name='password'
type='password'
inputSize={SIZE.LARGE}
onChange={this.setPassword}
value={this.state.password}
placeholder={intl.formatMessage({id: 'renderer.modals.login.loginModal.password', defaultMessage: 'Password'})}
/>
</Modal>
);
}

View File

@@ -1,9 +1,6 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import React, {useEffect, useState} from 'react';
import ReactDOM from 'react-dom';
@@ -42,7 +39,8 @@ const NewServerModalWrapper: React.FC = () => {
return (
<IntlProvider>
<NewServerModal
onClose={unremoveable ? undefined : onClose}
unremoveable={unremoveable}
onClose={onClose}
onSave={onSave}
editMode={false}
prefillURL={data?.prefillURL}

View File

@@ -8,9 +8,6 @@ import IntlProvider from 'renderer/intl_provider';
import type {PermissionModalInfo} from 'types/modals';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import PermissionModal from './permissionModal';
import setupDarkMode from '../darkMode';

View File

@@ -2,19 +2,19 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Modal, Button} from 'react-bootstrap';
import type {IntlShape} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import type {IntlShape} from 'react-intl';
import {PERMISSION_DESCRIPTION} from 'common/permissions';
import {parseURL} from 'common/utils/url';
import {t} from 'common/utils/util';
import {Modal} from 'renderer/components/Modal';
import type {PermissionModalInfo} from 'types/modals';
type Props = {
handleDeny: React.MouseEventHandler<HTMLButtonElement>;
handleGrant: React.MouseEventHandler<HTMLButtonElement>;
handleDeny: () => void;
handleGrant: () => void;
getPermissionInfo: () => Promise<PermissionModalInfo>;
openExternalLink: (protocol: string, url: string) => void;
intl: IntlShape;
@@ -67,74 +67,52 @@ class PermissionModal extends React.PureComponent<Props, State> {
};
return (
<div>
<p>
<FormattedMessage
id='renderer.modals.permission.permissionModal.body'
defaultMessage={'A site that\'s not included in your Mattermost server configuration requires access for {permission}.'}
values={{
permission: this.props.intl.formatMessage({id: PERMISSION_DESCRIPTION[permission!]}),
}}
/>
{}
</p>
<p>
<FormattedMessage
id='renderer.modals.permission.permissionModal.requestOriginatedFromOrigin'
defaultMessage='This request originated from <link>{origin}</link>'
values={{
origin: originDisplay,
link: (msg: React.ReactNode) => (
<a
<>
<FormattedMessage
id='renderer.modals.permission.permissionModal.body'
defaultMessage={'A site that\'s not included in your Mattermost server configuration requires access for {permission}.'}
values={{
permission: this.props.intl.formatMessage({id: PERMISSION_DESCRIPTION[permission!]}),
}}
/>
<p/>
<FormattedMessage
id='renderer.modals.permission.permissionModal.requestOriginatedFromOrigin'
defaultMessage='This request originated from <link>{origin}</link>'
values={{
origin: originDisplay,
link: (msg: React.ReactNode) => (
<a
onClick={click}
href='#'
>
{msg}
</a>
),
}}
/>
</p>
</div>
onClick={click}
href='#'
>
{msg}
</a>
),
}}
/>
</>
);
}
render() {
return (
<Modal
bsClass='modal'
className='permission-modal'
show={Boolean(this.state.url && this.state.permission)}
id='requestPermissionModal'
enforceFocus={true}
onHide={() => {}}
show={Boolean(this.state.url && this.state.permission)}
onExited={() => {}}
modalHeaderText={this.getModalTitle()}
handleConfirm={this.props.handleGrant}
confirmButtonText={
<FormattedMessage
id='label.accept'
defaultMessage='Accept'
/>
}
handleCancel={this.props.handleDeny}
>
<Modal.Header>
<Modal.Title>{this.getModalTitle()}</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.getModalBody()}
</Modal.Body>
<Modal.Footer className={'remove-border'}>
<div>
<Button onClick={this.props.handleDeny}>
<FormattedMessage
id='label.cancel'
defaultMessage='Cancel'
/>
</Button>
<Button
variant='primary'
onClick={this.props.handleGrant}
>
<FormattedMessage
id='label.accept'
defaultMessage='Accept'
/>
</Button>
</div>
</Modal.Footer>
{this.getModalBody()}
</Modal>
);
}

View File

@@ -1,10 +1,7 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/modals.css';
import React, {useEffect, useState} from 'react';
import React from 'react';
import ReactDOM from 'react-dom';
import IntlProvider from 'renderer/intl_provider';
@@ -23,19 +20,10 @@ const onSave = (data: boolean) => {
};
const RemoveServerModalWrapper: React.FC = () => {
const [serverName, setServerName] = useState<string>('');
useEffect(() => {
window.desktop.modals.getModalInfo<{name: string}>().then(({name}) => {
setServerName(name);
});
}, []);
return (
<IntlProvider>
<RemoveServerModal
show={true}
serverName={serverName}
onHide={() => {
onClose();
}}

View File

@@ -4,6 +4,7 @@
import 'bootstrap/dist/css/bootstrap.min.css';
import 'renderer/css/index.css';
import 'renderer/css/settings.css';
import 'renderer/css/modals-dark.scss';
import React from 'react';
import ReactDOM from 'react-dom';

View File

@@ -11,8 +11,6 @@ import type {UniqueServer} from 'types/config';
import ConfigureServer from '../../components/ConfigureServer';
import WelcomeScreen from '../../components/WelcomeScreen';
import 'bootstrap/dist/css/bootstrap.min.css';
const MOBILE_SCREEN_WIDTH = 1200;
const onConnect = (data: UniqueServer) => {