From c3493b09ff6c3f91bb8ea72d13321d8f49198e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Mondrag=C3=B3n?= <79058848+julmondragon@users.noreply.github.com> Date: Fri, 16 Sep 2022 10:35:37 -0500 Subject: [PATCH] MM-45981_Desktop: Add Server Screen: Improve Onboarding screens (#2243) --- .../configure_server_modal.test.js | 107 ++++++ .../startup/welcome_screen_modal.test.js | 9 +- i18n/en.json | 12 + src/common/utils/constants.ts | 1 + src/common/utils/url.ts | 5 + src/main/app/intercom.test.js | 3 +- src/main/app/intercom.ts | 12 +- src/renderer/assets/svg/womanLaptop.svg | 198 ++++++++++ src/renderer/components/ConfigureServer.tsx | 361 ++++++++++++++++++ src/renderer/components/Input.tsx | 197 ++++++++++ .../components/SaveButton/LoadingSpinner.tsx | 28 ++ .../components/SaveButton/LoadingWrapper.tsx | 29 ++ .../components/SaveButton/SaveButton.tsx | 74 ++++ .../WelcomeScreen/WelcomeScreen.tsx | 79 ++-- src/renderer/css/_css_variables.scss | 13 + src/renderer/css/_mixins.scss | 81 ++++ src/renderer/css/components/Button.scss | 48 ++- src/renderer/css/components/Carousel.scss | 60 +-- .../css/components/ConfigureServer.scss | 341 +++++++++++++++++ src/renderer/css/components/Input.scss | 254 ++++++++++++ .../css/components/LoadingSpinner.scss | 36 ++ .../css/components/WelcomeScreen.scss | 9 + src/renderer/css/fonts.css | 7 + .../modals/welcomeScreen/welcomeScreen.tsx | 56 ++- 24 files changed, 1920 insertions(+), 100 deletions(-) create mode 100644 e2e/specs/server_management/configure_server_modal.test.js create mode 100644 src/renderer/assets/svg/womanLaptop.svg create mode 100644 src/renderer/components/ConfigureServer.tsx create mode 100644 src/renderer/components/Input.tsx create mode 100644 src/renderer/components/SaveButton/LoadingSpinner.tsx create mode 100644 src/renderer/components/SaveButton/LoadingWrapper.tsx create mode 100644 src/renderer/components/SaveButton/SaveButton.tsx create mode 100644 src/renderer/css/_mixins.scss create mode 100644 src/renderer/css/components/ConfigureServer.scss create mode 100644 src/renderer/css/components/Input.scss create mode 100644 src/renderer/css/components/LoadingSpinner.scss diff --git a/e2e/specs/server_management/configure_server_modal.test.js b/e2e/specs/server_management/configure_server_modal.test.js new file mode 100644 index 00000000..96756209 --- /dev/null +++ b/e2e/specs/server_management/configure_server_modal.test.js @@ -0,0 +1,107 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +'use strict'; + +const fs = require('fs'); + +const env = require('../../modules/environment'); +const {asyncSleep} = require('../../modules/utils'); + +describe('Configure Server Modal', function desc() { + this.timeout(30000); + + beforeEach(async () => { + env.createTestUserDataDir(); + env.cleanTestConfig(); + await asyncSleep(1000); + + this.app = await env.getApp(); + + configureServerModal = this.app.windows().find((window) => window.url().includes('welcomeScreen')); + await configureServerModal.click('#getStartedWelcomeScreen'); + + await asyncSleep(1000); + }); + + afterEach(async () => { + if (this.app) { + await this.app.close(); + } + await env.clearElectronInstances(); + }); + + let configureServerModal; + + it('MM-T5115 should not be valid if no display name has been set', async () => { + await configureServerModal.type('#input_name', ''); + + const connectButtonDisabled = await configureServerModal.getAttribute('#connectConfigureServer', 'disabled'); + (connectButtonDisabled === '').should.be.true; + }); + + it('MM-T5116 should not be valid if no URL has been set', async () => { + await configureServerModal.type('#input_url', ''); + + const connectButtonDisabled = await configureServerModal.getAttribute('#connectConfigureServer', 'disabled'); + (connectButtonDisabled === '').should.be.true; + }); + + it('MM-T5117 should be valid if display name and URL are set', async () => { + await configureServerModal.type('#input_name', 'TestTeam'); + await configureServerModal.type('#input_url', 'http://example.org'); + + const connectButtonDisabled = await configureServerModal.getAttribute('#connectConfigureServer', 'disabled'); + (connectButtonDisabled === '').should.be.false; + }); + + it('MM-T5118 should not be valid if an invalid URL has been set', async () => { + await configureServerModal.type('#input_name', 'TestTeam'); + await configureServerModal.type('#input_url', 'lorem.ipsum.dolor.sit.amet'); + + await configureServerModal.click('#connectConfigureServer'); + + await asyncSleep(1000); + + const errorClass = await configureServerModal.getAttribute('#customMessage_url', 'class'); + errorClass.should.contain('Input___error'); + + const connectButtonDisabled = await configureServerModal.getAttribute('#connectConfigureServer', 'disabled'); + (connectButtonDisabled === '').should.be.true; + }); + + it('MM-T5119 should add the team to the config file', async () => { + await configureServerModal.type('#input_name', 'TestTeam'); + await configureServerModal.type('#input_url', 'http://example.org'); + + await configureServerModal.click('#connectConfigureServer'); + + await asyncSleep(1000); + + const existing = Boolean(await this.app.windows().find((window) => window.url().includes('welcomeScreen'))); + existing.should.be.false; + + const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8')); + savedConfig.teams.should.deep.contain({ + url: 'http://example.org', + name: 'TestTeam', + index: null, + order: 0, + tabs: [ + { + name: 'TAB_MESSAGING', + order: 0, + isOpen: true, + }, + { + name: 'TAB_FOCALBOARD', + order: 1, + }, + { + name: 'TAB_PLAYBOOKS', + order: 2, + }, + ], + }); + }); +}); diff --git a/e2e/specs/startup/welcome_screen_modal.test.js b/e2e/specs/startup/welcome_screen_modal.test.js index 892012b0..297319d9 100644 --- a/e2e/specs/startup/welcome_screen_modal.test.js +++ b/e2e/specs/startup/welcome_screen_modal.test.js @@ -133,10 +133,9 @@ describe('Welcome Screen Modal', function desc() { it('MM-T4983 should be able to click the get started button and be redirected to new server modal', async () => { await welcomeScreenModal.click('#getStartedWelcomeScreen'); - const newServerModal = await this.app.waitForEvent('window', { - predicate: (window) => window.url().includes('newServer'), - }); - const modalTitle = await newServerModal.innerText('#newServerModal .modal-title'); - modalTitle.should.equal('Add Server'); + await asyncSleep(1000); + + const modalCardTitle = await welcomeScreenModal.innerText('.ConfigureServer .ConfigureServer__card-title'); + modalCardTitle.should.equal('Enter your server details'); }); }); diff --git a/i18n/en.json b/i18n/en.json index fd3be2c2..eb8efeab 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -122,6 +122,15 @@ "main.windows.mainWindow.minimizeToTray.dialog.title": "Minimize to Tray", "renderer.components.autoSaveIndicator.saved": "Saved", "renderer.components.autoSaveIndicator.saving": "Saving...", + "renderer.components.configureServer.cardtitle": "Enter your server details", + "renderer.components.configureServer.connect.default": "Connect", + "renderer.components.configureServer.connect.saving": "Connecting…", + "renderer.components.configureServer.name.info": "The name that will be displayed in your server list", + "renderer.components.configureServer.name.placeholder": "Server display name", + "renderer.components.configureServer.subtitle": "Set up your first server to connect to your

team’s communication hub", + "renderer.components.configureServer.title": "Let’s connect to a server", + "renderer.components.configureServer.url.info": "The URL of your Mattermost server", + "renderer.components.configureServer.url.placeholder": "Server URL", "renderer.components.errorView.cannotConnectToAppName": "Cannot connect to {appName}", "renderer.components.errorView.havingTroubleConnecting": "We're having trouble connecting to {appName}. We'll continue to try and establish a connection.", "renderer.components.errorView.refreshThenVerify": "If refreshing this page (Ctrl+R or Command+R) does not work please verify that:", @@ -129,6 +138,7 @@ "renderer.components.errorView.troubleshooting.computerIsConnected": "Your computer is connected to the internet.", "renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect": "The {appName} URL {url} is correct", "renderer.components.extraBar.back": "Back", + "renderer.components.input.required": "This field is required", "renderer.components.mainPage.contextMenu.ariaLabel": "Context menu", "renderer.components.mainPage.downloadingUpdate": "Downloading update. {percentDone}% of {total} @ {speed}/s", "renderer.components.mainPage.titleBar": "Mattermost", @@ -149,6 +159,8 @@ "renderer.components.removeServerModal.body": "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.", "renderer.components.removeServerModal.confirm": "Confirm you wish to remove the {serverName} server?", "renderer.components.removeServerModal.title": "Remove Server", + "renderer.components.saveButton.save": "Save", + "renderer.components.saveButton.saving": "Saving", "renderer.components.settingsPage.afterRestart": "Setting takes effect after restarting the app.", "renderer.components.settingsPage.appLanguage": "Set app language (beta)", "renderer.components.settingsPage.appLanguage.description": "Chooses the language that the Desktop App will use for menu items and popups. Still in beta, some languages will be missing translation strings.", diff --git a/src/common/utils/constants.ts b/src/common/utils/constants.ts index 88236598..8c0092bd 100644 --- a/src/common/utils/constants.ts +++ b/src/common/utils/constants.ts @@ -7,6 +7,7 @@ export const DEVELOPMENT = 'development'; export const SECOND = 1000; export const RELOAD_INTERVAL = 5 * SECOND; +export const MODAL_TRANSITION_TIMEOUT = 400; export const MAX_SERVER_RETRIES = 3; diff --git a/src/common/utils/url.ts b/src/common/utils/url.ts index eb7ae9eb..7abc856a 100644 --- a/src/common/utils/url.ts +++ b/src/common/utils/url.ts @@ -19,6 +19,10 @@ function isValidURI(testURL: string) { return Boolean(isUri(testURL)); } +function startsWithProtocol(testURL: string) { + return Boolean((/^https?:\/\/.*/).test(testURL.trim())); +} + function parseURL(inputURL: URL | string) { if (inputURL instanceof URL) { return inputURL; @@ -237,4 +241,5 @@ export default { isChannelExportUrl, isUrlType, cleanPathName, + startsWithProtocol, }; diff --git a/src/main/app/intercom.test.js b/src/main/app/intercom.test.js index a78066eb..f07e08a9 100644 --- a/src/main/app/intercom.test.js +++ b/src/main/app/intercom.test.js @@ -246,6 +246,7 @@ describe('main/app/intercom', () => { Config.set.mockImplementation((name, value) => { Config[name] = value; }); + Config.teams = JSON.parse(JSON.stringify([])); }); it('should show welcomeScreen modal', async () => { @@ -253,7 +254,7 @@ describe('main/app/intercom', () => { ModalManager.addModal.mockReturnValue(promise); handleWelcomeScreenModal(); - expect(ModalManager.addModal).toHaveBeenCalledWith('welcomeScreen', '/some/index.html', '/some/preload.js', {}, {}, true); + expect(ModalManager.addModal).toHaveBeenCalledWith('welcomeScreen', '/some/index.html', '/some/preload.js', [], {}, true); }); }); }); diff --git a/src/main/app/intercom.ts b/src/main/app/intercom.ts index 3e899164..25b24e94 100644 --- a/src/main/app/intercom.ts +++ b/src/main/app/intercom.ts @@ -238,10 +238,16 @@ export function handleWelcomeScreenModal() { if (!mainWindow) { return; } - const modalPromise = ModalManager.addModal('welcomeScreen', html, modalPreload, {}, mainWindow, true); + const modalPromise = ModalManager.addModal('welcomeScreen', html, modalPreload, Config.teams.map((team, index) => ({...team, index})), mainWindow, Config.teams.length === 0); if (modalPromise) { - modalPromise.then(() => { - handleNewServerModal(); + modalPromise.then((data) => { + const teams = Config.teams; + const order = teams.length; + const newTeam = getDefaultTeamWithTabsFromTeam({...data, order}); + teams.push(newTeam); + Config.set('teams', teams); + updateServerInfos([newTeam]); + WindowManager.switchServer(newTeam.name, true); }).catch((e) => { // e is undefined for user cancellation if (e) { diff --git a/src/renderer/assets/svg/womanLaptop.svg b/src/renderer/assets/svg/womanLaptop.svg new file mode 100644 index 00000000..9e25e472 --- /dev/null +++ b/src/renderer/assets/svg/womanLaptop.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/renderer/components/ConfigureServer.tsx b/src/renderer/components/ConfigureServer.tsx new file mode 100644 index 00000000..37765888 --- /dev/null +++ b/src/renderer/components/ConfigureServer.tsx @@ -0,0 +1,361 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useState, useCallback, useEffect} from 'react'; +import {useIntl, FormattedMessage} from 'react-intl'; +import classNames from 'classnames'; + +import {TeamWithIndex} from 'types/config'; + +import womanLaptop from 'renderer/assets/svg/womanLaptop.svg'; + +import Header from 'renderer/components/Header'; +import Input, {STATUS, SIZE} from 'renderer/components/Input'; +import LoadingBackground from 'renderer/components/LoadingScreen/LoadingBackground'; +import SaveButton from 'renderer/components/SaveButton/SaveButton'; + +import {PING_DOMAIN, PING_DOMAIN_RESPONSE} from 'common/communication'; +import {MODAL_TRANSITION_TIMEOUT} from 'common/utils/constants'; +import urlUtils from 'common/utils/url'; + +import 'renderer/css/components/Button.scss'; +import 'renderer/css/components/ConfigureServer.scss'; +import 'renderer/css/components/LoadingScreen.css'; + +type ConfigureServerProps = { + currentTeams: TeamWithIndex[]; + team?: TeamWithIndex; + mobileView?: boolean; + darkMode?: boolean; + messageTitle?: string; + messageSubtitle?: string; + cardTitle?: string; + alternateLinkMessage?: string; + alternateLinkText?: string; + alternateLinkURL?: string; + onConnect: (data: TeamWithIndex) => void; +}; + +function ConfigureServer({ + currentTeams, + team, + mobileView, + darkMode, + messageTitle, + messageSubtitle, + cardTitle, + alternateLinkMessage, + alternateLinkText, + alternateLinkURL, + onConnect, +}: ConfigureServerProps) { + const {formatMessage} = useIntl(); + + const { + name: prevName, + url: prevURL, + order = 0, + index = NaN, + } = team || {}; + + const [transition, setTransition] = useState<'inFromRight' | 'outToLeft'>(); + const [name, setName] = useState(prevName || ''); + const [url, setUrl] = useState(prevURL || ''); + const [nameError, setNameError] = useState(''); + const [urlError, setURLError] = useState(''); + const [showContent, setShowContent] = useState(false); + const [waiting, setWaiting] = useState(false); + + const canSave = name && url && !nameError && !urlError; + + useEffect(() => { + setTransition('inFromRight'); + setShowContent(true); + }, []); + + const checkProtocolInURL = (checkURL: string): Promise => { + if (urlUtils.startsWithProtocol(checkURL)) { + return Promise.resolve(checkURL); + } + + return new Promise((resolve) => { + let eventCount = 0; + + const handler = (event: {data: {type: string; data: string | Error}}) => { + let newURL = checkURL; + + if (event.data.type === PING_DOMAIN_RESPONSE) { + if (event.data.data instanceof Error) { + console.error(`Could not ping url: ${checkURL}`); + } else { + newURL = `${event.data.data}://${checkURL}`; + setUrl(newURL); + } + + window.removeEventListener('message', handler); + resolve(newURL); + } else if (eventCount >= 3) { + window.removeEventListener('message', handler); + resolve(newURL); + } + + eventCount++; + }; + + window.addEventListener('message', handler); + window.postMessage({type: PING_DOMAIN, data: checkURL}, window.location.href); + }); + }; + + const validateName = () => { + const newName = name.trim(); + + if (!newName) { + return formatMessage({ + id: 'renderer.components.newTeamModal.error.nameRequired', + defaultMessage: 'Name is required.', + }); + } + + if (currentTeams.find(({name: existingName}) => existingName === newName)) { + return formatMessage({ + id: 'renderer.components.newTeamModal.error.serverNameExists', + defaultMessage: 'A server with the same name already exists.', + }); + } + + return ''; + }; + + const validateURL = async (fullURL: string) => { + if (!fullURL) { + return formatMessage({ + id: 'renderer.components.newTeamModal.error.urlRequired', + defaultMessage: 'URL is required.', + }); + } + + if (!urlUtils.startsWithProtocol(fullURL)) { + return formatMessage({ + id: 'renderer.components.newTeamModal.error.urlNeedsHttp', + defaultMessage: 'URL should start with http:// or https://.', + }); + } + + if (!urlUtils.isValidURL(fullURL)) { + return formatMessage({ + id: 'renderer.components.newTeamModal.error.urlIncorrectFormatting', + defaultMessage: 'URL is not formatted correctly.', + }); + } + + if (currentTeams.find(({url: existingURL}) => existingURL === fullURL)) { + return formatMessage({ + id: 'renderer.components.newTeamModal.error.serverUrlExists', + defaultMessage: 'A server with the same URL already exists.', + }); + } + + return ''; + }; + + const handleNameOnChange = ({target: {value}}: React.ChangeEvent) => { + setName(value); + + if (nameError) { + setNameError(''); + } + }; + + const handleURLOnChange = ({target: {value}}: React.ChangeEvent) => { + setUrl(value); + + if (urlError) { + setURLError(''); + } + }; + + const handleOnSaveButtonClick = (e: React.MouseEvent) => { + submit(e); + }; + + const handleOnCardEnterKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + submit(e); + } + }; + + const submit = async (e: React.MouseEvent | React.KeyboardEvent) => { + e.preventDefault(); + + if (!canSave || waiting) { + return; + } + + setWaiting(true); + + const nameError = validateName(); + + if (nameError) { + setTransition(undefined); + setNameError(nameError); + setWaiting(false); + return; + } + + const fullURL = await checkProtocolInURL(url.trim()); + const urlError = await validateURL(fullURL); + + if (urlError) { + setTransition(undefined); + setURLError(urlError); + setWaiting(false); + return; + } + + setTransition('outToLeft'); + + setTimeout(() => { + onConnect({ + url: fullURL, + name, + index, + order, + }); + }, MODAL_TRANSITION_TIMEOUT); + }; + + const getAlternateLink = useCallback(() => { + if (!alternateLinkURL || !alternateLinkMessage || !alternateLinkText) { + return undefined; + } + + return ( +
+ + {alternateLinkMessage} + + + {alternateLinkText} + +
+ ); + }, [transition, darkMode, alternateLinkURL, alternateLinkMessage, alternateLinkText]); + + return ( +
+ +
+ {showContent && ( +
+ {!mobileView && getAlternateLink()} +
+
+

+ {messageTitle || formatMessage({id: 'renderer.components.configureServer.title', defaultMessage: 'Let’s connect to a server'})} +

+

+ {messageSubtitle || ( + (<>
{x}), + }} + />) + } +

+
+ +
+
+
+
+

+ {cardTitle || formatMessage({id: 'renderer.components.configureServer.cardtitle', defaultMessage: 'Enter your server details'})} +

+
+ + + +
+
+
+
+
+ )} +
+
+ ); +} + +export default ConfigureServer; diff --git a/src/renderer/components/Input.tsx b/src/renderer/components/Input.tsx new file mode 100644 index 00000000..6a438fa2 --- /dev/null +++ b/src/renderer/components/Input.tsx @@ -0,0 +1,197 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useState, useEffect} from 'react'; +import {useIntl} from 'react-intl'; + +import classNames from 'classnames'; + +import 'renderer/css/components/Input.scss'; + +export enum STATUS { + NONE = 'none', + SUCCESS = 'success', + INFO = 'info', + WARNING = 'warning', + ERROR = 'error', +} + +export enum SIZE { + MEDIUM = 'medium', + LARGE = 'large', +} + +export type CustomMessageInputType = { + type: 'info' | 'error' | 'warning' | 'success'; + value: string; +} | null; + +interface InputProps extends React.InputHTMLAttributes { + required?: boolean; + hasError?: boolean; + addon?: React.ReactElement; + textPrefix?: string; + inputPrefix?: JSX.Element; + inputSuffix?: JSX.Element; + label?: string; + containerClassName?: string; + wrapperClassName?: string; + inputClassName?: string; + limit?: number; + useLegend?: boolean; + customMessage?: CustomMessageInputType; + inputSize?: SIZE; + darkMode?: boolean; +} + +const Input = React.forwardRef(( + { + name, + value, + label, + placeholder, + useLegend = true, + className, + hasError, + required, + addon, + textPrefix, + inputPrefix, + inputSuffix, + containerClassName, + wrapperClassName, + inputClassName, + limit, + customMessage, + maxLength, + inputSize = SIZE.MEDIUM, + disabled, + darkMode, + onFocus, + onBlur, + onChange, + ...otherProps + }: InputProps, + ref?: React.Ref, +) => { + const {formatMessage} = useIntl(); + + const [focused, setFocused] = useState(false); + const [customInputLabel, setCustomInputLabel] = useState(null); + + useEffect(() => { + if (customMessage !== undefined && customMessage !== null && customMessage.value !== '') { + setCustomInputLabel(customMessage); + } + }, [customMessage]); + + const handleOnFocus = (event: React.FocusEvent) => { + setFocused(true); + + if (onFocus) { + onFocus(event); + } + }; + + const handleOnBlur = (event: React.FocusEvent) => { + setFocused(false); + validateInput(); + + if (onBlur) { + onBlur(event); + } + }; + + const handleOnChange = (event: React.ChangeEvent) => { + setCustomInputLabel(null); + + if (onChange) { + onChange(event); + } + }; + + const validateInput = () => { + if (!required || (value !== null && value !== '')) { + return; + } + const validationErrorMsg = formatMessage({id: 'renderer.components.input.required', defaultMessage: 'This field is required'}); + setCustomInputLabel({type: STATUS.ERROR, value: validationErrorMsg}); + }; + + const showLegend = Boolean(focused || value); + const error = customInputLabel?.type === 'error'; + const limitExceeded = limit && value && !Array.isArray(value) ? value.toString().length - limit : 0; + + return ( +
+
0, + Input_fieldset___legend: showLegend, + })} + > + {useLegend && ( + + {showLegend ? label || placeholder : null} + + )} +
+ {inputPrefix} + {textPrefix && {textPrefix}} + + {limitExceeded > 0 && ( + + {'-'}{limitExceeded} + + )} + {inputSuffix} +
+ {addon} +
+ {customInputLabel && ( +
+ {customInputLabel.type !== STATUS.INFO && ( + + )} + {customInputLabel.value} +
+ )} +
+ ); +}); + +export default Input; diff --git a/src/renderer/components/SaveButton/LoadingSpinner.tsx b/src/renderer/components/SaveButton/LoadingSpinner.tsx new file mode 100644 index 00000000..5704950d --- /dev/null +++ b/src/renderer/components/SaveButton/LoadingSpinner.tsx @@ -0,0 +1,28 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import 'renderer/css/components/LoadingSpinner.scss'; + +type Props = { + text: React.ReactNode; +} + +export default class LoadingSpinner extends React.PureComponent { + public static defaultProps: Props = { + text: null, + } + + public render() { + return ( + + + {this.props.text} + + ); + } +} diff --git a/src/renderer/components/SaveButton/LoadingWrapper.tsx b/src/renderer/components/SaveButton/LoadingWrapper.tsx new file mode 100644 index 00000000..ff305e9b --- /dev/null +++ b/src/renderer/components/SaveButton/LoadingWrapper.tsx @@ -0,0 +1,29 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import LoadingSpinner from './LoadingSpinner'; + +type Props = { + loading: boolean; + text: React.ReactNode; + children: React.ReactNode; +} + +export default class LoadingWrapper extends React.PureComponent { + public static defaultProps: Props = { + loading: true, + text: null, + children: null, + } + + public render() { + const {text, loading, children} = this.props; + if (!loading) { + return children; + } + + return ; + } +} diff --git a/src/renderer/components/SaveButton/SaveButton.tsx b/src/renderer/components/SaveButton/SaveButton.tsx new file mode 100644 index 00000000..d2745756 --- /dev/null +++ b/src/renderer/components/SaveButton/SaveButton.tsx @@ -0,0 +1,74 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; +import classNames from 'classnames'; + +import LoadingWrapper from './LoadingWrapper'; + +import 'renderer/css/components/Button.scss'; + +type Props = { + saving: boolean; + disabled?: boolean; + id?: string; + onClick: (e: React.MouseEvent) => void; + savingMessage?: React.ReactNode; + defaultMessage?: React.ReactNode; + extraClasses?: string; + darkMode?: boolean; +} + +const SaveButton = ({ + id, + defaultMessage = ( + + ), + disabled, + extraClasses, + saving, + savingMessage = ( + + ), + darkMode, + onClick, +}: Props) => { + const handleOnClick = (e: React.MouseEvent) => { + if (saving) { + return; + } + + onClick(e); + }; + + return ( + + ); +}; + +export default SaveButton; diff --git a/src/renderer/components/WelcomeScreen/WelcomeScreen.tsx b/src/renderer/components/WelcomeScreen/WelcomeScreen.tsx index dc031d3c..4989a7e8 100644 --- a/src/renderer/components/WelcomeScreen/WelcomeScreen.tsx +++ b/src/renderer/components/WelcomeScreen/WelcomeScreen.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useMemo} from 'react'; +import React, {useState, useEffect, useMemo} from 'react'; import {useIntl, FormattedMessage} from 'react-intl'; import classNames from 'classnames'; @@ -14,6 +14,8 @@ import Carousel from 'renderer/components/Carousel'; import Header from 'renderer/components/Header'; import LoadingBackground from 'renderer/components/LoadingScreen/LoadingBackground'; +import {MODAL_TRANSITION_TIMEOUT} from 'common/utils/constants'; + import WelcomeScreenSlide from './WelcomeScreenSlide'; import 'renderer/css/components/Button.scss'; @@ -31,6 +33,13 @@ function WelcomeScreen({ }: WelcomeScreenProps) { const {formatMessage} = useIntl(); + const [transition, setTransition] = useState<'outToLeft'>(); + const [showContent, setShowContent] = useState(false); + + useEffect(() => { + setShowContent(true); + }, []); + const slides = useMemo(() => [ { key: 'welcome', @@ -97,7 +106,11 @@ function WelcomeScreen({ ], []); const handleOnGetStartedClick = () => { - onGetStarted(); + setTransition('outToLeft'); + + setTimeout(() => { + onGetStarted(); + }, MODAL_TRANSITION_TIMEOUT); }; return ( @@ -110,37 +123,39 @@ function WelcomeScreen({ >
-
-
- ({ - key, - content: ( - - ), - }))} - darkMode={darkMode} - /> - + {showContent && ( +
+
+ ({ + key, + content: ( + + ), + }))} + darkMode={darkMode} + /> + +
-
+ )}
); diff --git a/src/renderer/css/_css_variables.scss b/src/renderer/css/_css_variables.scss index af5a55a1..26a07fab 100644 --- a/src/renderer/css/_css_variables.scss +++ b/src/renderer/css/_css_variables.scss @@ -1,14 +1,27 @@ :root { + --away-indicator-dark: #c79e3f; --button-bg: #166de0; --button-color: #fff; + --center-channel-bg: #fff; + --center-channel-color: #3d3c40; --center-channel-text: #3f4350; + --error-text: #d24b4e; + --link-color: #2389d7; + --online-indicator: #06d6a0; --sidebar-text-active-border: #579eff; --denim-button-bg: #1c58d9; --denim-sidebar-active-border: #5d89ea; + --denim-sidebar-header-bg: #192A4D; --title-color-indigo-500: #1e325c; --button-color-rgb: 255, 255, 255; --center-channel-color-rgb: 61, 60, 64; --center-channel-text-rgb: 63, 67, 80; + --link-color-inverted-rgb: 129, 163, 239; --denim-button-bg-rgb: 28, 88, 217; + --denim-sidebar-header-bg-rgb: 25, 42, 77; + --secondary-blue-rgb: 34, 64, 109; + --onyx-center-channel-text-rgb: 221, 223, 228; + + --elevation-5: 0 12px 32px 0 rgba(0, 0, 0, 0.12); } diff --git a/src/renderer/css/_mixins.scss b/src/renderer/css/_mixins.scss new file mode 100644 index 00000000..d2e24ab6 --- /dev/null +++ b/src/renderer/css/_mixins.scss @@ -0,0 +1,81 @@ +@mixin inFromRight($start) { + @keyframes inFromRight#{$start} { + 0% { + transform: translateX(#{$start + '%'}); + opacity: 0; + } + 100% { + transform: translateX(0%); + opacity: 1; + } + } +} + +@mixin inFromLeft($start) { + @keyframes inFromLeft#{$start} { + 0% { + transform: translateX(#{'-' + $start + '%'}); + opacity: 0; + } + 100% { + transform: translateX(0%); + opacity: 1; + } + } +} + +@mixin outToLeft($end) { + @keyframes outToLeft#{$end} { + 0% { + transform: translateX(0%); + opacity: 1; + } + 100% { + transform: translateX(#{'-' + $end + '%'}); + opacity: 0; + } + } +} + +@mixin outToRight($end) { + @keyframes outToRight#{$end} { + 0% { + transform: translateX(0%); + opacity: 1; + } + 100% { + transform: translateX(#{$end + '%'}); + opacity: 0; + } + } +} + +@mixin shake-horizontally { + animation: shake-horizontally 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; + backface-visibility: hidden; + perspective: 1000px; + transform: translate3d(0, 0, 0); +} + +@keyframes shake-horizontally { + 10%, + 90% { + transform: translate3d(-1px, 0, 0); + } + + 20%, + 80% { + transform: translate3d(2px, 0, 0); + } + + 30%, + 50%, + 70% { + transform: translate3d(-4px, 0, 0); + } + + 40%, + 60% { + transform: translate3d(4px, 0, 0); + } +} diff --git a/src/renderer/css/components/Button.scss b/src/renderer/css/components/Button.scss index 5a626043..a11bfc8a 100644 --- a/src/renderer/css/components/Button.scss +++ b/src/renderer/css/components/Button.scss @@ -58,8 +58,8 @@ } &:disabled { - background: rgba(var(--center-channel-text-rgb), 0.08); - color: rgba(var(--center-channel-text-rgb), 0.32); + background: rgba(var(--button-color-rgb), 0.08); + color: rgba(var(--button-color-rgb), 0.32); } } @@ -85,6 +85,50 @@ } } +.link-button { + border: none; + box-shadow: none; + color: var(--link-color); + + &:hover, + &:active, + &:focus, + &:focus-visible { + text-decoration-line: underline; + } + + &:focus-visible { + outline: 0; + } + + &:disabled { + color: rgba(var(--center-channel-color-rgb), 0.32); + cursor: not-allowed; + text-decoration: none; + } +} + +.link-button-inverted { + color: rgb(var(--link-color-inverted-rgb)); + + + &:hover, + &:active, + &:focus { + color: var(--denim-sidebar-active-border); + } + + &:disabled { + color: rgba(var(--link-color-inverted-rgb), 0.32); + } +} + +.link-small-button { + font-size: 12px; + font-weight: 600; + line-height: 16px; +} + .icon-button { position: relative; display: inline-flex; diff --git a/src/renderer/css/components/Carousel.scss b/src/renderer/css/components/Carousel.scss index a23714e8..1f167118 100644 --- a/src/renderer/css/components/Carousel.scss +++ b/src/renderer/css/components/Carousel.scss @@ -1,3 +1,5 @@ +@import '../_mixins.scss'; + .Carousel { display: flex; flex: 1; @@ -34,61 +36,25 @@ } .inFromRight { - animation: inFromRight 0.4s ease-in-out; + @include inFromRight(30); + + animation: inFromRight30 0.4s ease-in-out; } .inFromLeft { - animation: inFromLeft 0.4s ease-in-out; + @include inFromLeft(30); + + animation: inFromLeft30 0.4s ease-in-out; } .outToRight { - animation: outToRight 0.4s ease-in-out; + @include outToRight(30); + + animation: outToRight30 0.4s ease-in-out; } .outToLeft { - animation: outToLeft 0.4s ease-in-out; -} + @include outToLeft(30); -@keyframes inFromRight { - 0% { - transform: translateX(30%); - opacity: 0; - } - 100% { - transform: translateX(0%); - opacity: 1; - } + animation: outToLeft30 0.4s ease-in-out; } - -@keyframes inFromLeft { - 0% { - transform: translateX(-30%); - opacity: 0; - } - 100% { - transform: translateX(0%); - opacity: 1; - } -} - -@keyframes outToRight { - 0% { - transform: translateX(0%); - opacity: 1; - } - 100% { - transform: translateX(30%); - opacity: 0; - } -} - -@keyframes outToLeft { - 0% { - transform: translateX(0%); - opacity: 1; - } - 100% { - transform: translateX(-30%); - opacity: 0; - } -} \ No newline at end of file diff --git a/src/renderer/css/components/ConfigureServer.scss b/src/renderer/css/components/ConfigureServer.scss new file mode 100644 index 00000000..8a67624f --- /dev/null +++ b/src/renderer/css/components/ConfigureServer.scss @@ -0,0 +1,341 @@ +@import url("../_css_variables.scss"); +@import url("../fonts.css"); +@import '../_mixins.scss'; + +.alternate-link { + margin-left: auto; + margin-bottom: 18px; + font-family: 'Open Sans'; + + .alternate-link__message { + color: var(--center-channel-text); + font-size: 12px; + font-weight: 400; + line-height: 16px; + } + + .alternate-link__link { + padding-left: 4px; + } + + &.inFromRight { + @include inFromRight(40); + + animation: inFromRight40 0.5s ease-in-out; + } + + &.outToLeft { + @include outToLeft(40); + + animation: outToLeft40 0.4s ease-in-out; + opacity: 0; + } +} + +.alternate-link-inverted { + .alternate-link__message { + color: var(--button-color); + } +} + +.ConfigureServer { + flex-direction: column; + z-index: 20; + + * { + z-index: 21; + } + + .ConfigureServer__body { + display: flex; + flex-direction: column; + margin: auto; + font-family: 'Open Sans'; + + .ConfigureServer__content { + display: flex; + height: fit-content; + flex: 1; + align-items: center; + justify-content: center; + margin-bottom: 42px; + + .ConfigureServer__message { + display: flex; + width: 540px; + flex-flow: column; + align-self: flex-start; + z-index: 22; + -webkit-font-smoothing: antialiased; + + .ConfigureServer__message-title { + padding-right: 60px; + color: var(--title-color-indigo-500); + font-family: 'Metropolis'; + font-size: 80px; + font-weight: 600; + letter-spacing: -0.05em; + line-height: 88px; + } + + .ConfigureServer__message-subtitle { + color: rgba(var(--center-channel-text-rgb), 0.72); + font-size: 18px; + font-weight: 400; + line-height: 28px; + } + + .ConfigureServer__message-img { + position: relative; + align-self: flex-end; + + img { + position: absolute; + bottom: -230px; + left: -225px; + display: block; + } + } + + &.inFromRight { + .ConfigureServer__message-title, + .ConfigureServer__message-subtitle { + @include inFromRight(30); + + animation: inFromRight30 0.5s ease-in-out; + } + + .ConfigureServer__message-img img { + @include inFromRight(70); + + animation: inFromRight70 0.5s ease-in-out; + } + } + + &.outToLeft { + .ConfigureServer__message-title, + .ConfigureServer__message-subtitle { + @include outToLeft(30); + + animation: outToLeft30 0.4s ease-in-out; + opacity: 0; + } + + .ConfigureServer__message-img img { + @include outToLeft(70); + + animation: outToLeft70 0.4s ease-in-out; + opacity: 0; + } + } + } + + .ConfigureServer__card { + width: 540px; + box-sizing: border-box; + border: 1px solid rgba(var(--center-channel-text-rgb), 0.08); + margin-left: 60px; + background-color: var(--center-channel-bg); + border-radius: 8px; + box-shadow: var(--elevation-5); + + .ConfigureServer__card-content { + display: flex; + flex: 1; + flex-flow: column; + padding: 48px 56px; + border: none; + box-shadow: none; + + &:focus-visible { + outline: 0; + } + + .ConfigureServer__card-title { + color: var(--center-channel-text); + font-family: 'Metropolis'; + font-size: 22px; + font-style: normal; + font-weight: 600; + line-height: 28px; + margin-bottom: 32px; + } + + .ConfigureServer__card-form { + .ConfigureServer__card-form-input-container { + margin-top: 22px; + } + + .ConfigureServer__card-form-button { + width: 100%; + margin-top: 30px; + } + } + } + + &.inFromRight { + @include inFromRight(40); + + animation: inFromRight40 0.5s ease-in-out; + } + + &.outToLeft { + @include outToLeft(40); + + animation: outToLeft40 0.4s ease-in-out; + opacity: 0; + } + + &.with-error { + @include shake-horizontally; + } + } + } + } + + .ConfigureServer__footer { + display: block; + width: 100%; + height: 100px; + } +} + +.ConfigureServer-inverted { + .ConfigureServer__body .ConfigureServer__content { + .ConfigureServer__message { + .ConfigureServer__message-title { + color: var(--button-color); + } + + .ConfigureServer__message-subtitle { + color: rgba(var(--button-color-rgb), 0.72); + } + } + + .ConfigureServer__card { + border: 1px solid rgba(var(--button-color-rgb), 0.08); + background-color: var(--denim-sidebar-header-bg); + + .ConfigureServer__card-content .ConfigureServer__card-title { + color: var(--button-color); + } + } + } +} + +@media screen and (min-width: 1680px) { + .ConfigureServer { + .ConfigureServer__body .ConfigureServer__content { + .ConfigureServer__message, + .ConfigureServer__card { + width: 610px; + } + + .ConfigureServer__message .ConfigureServer__message-title { + padding-right: 130px; + margin-top: 48px; + } + } + } +} + +@media screen and (max-width: 1199px) { + .alternate-link { + margin-bottom: 0; + } + + .ConfigureServer { + .ConfigureServer__body .ConfigureServer__content { + flex-direction: column; + + .ConfigureServer__message, + .ConfigureServer__card { + width: 640px; + } + + .ConfigureServer__message { + align-self: center; + padding: 24px; + + .ConfigureServer__message-title { + padding-right: 210px; + font-size: 64px; + line-height: 76px; + } + + .ConfigureServer__message-subtitle { + margin: 0; + } + + .ConfigureServer__message-img { + display: none; + } + } + + .ConfigureServer__card { + border: none; + margin: 0; + background-color: unset; + box-shadow: none; + + .ConfigureServer__card-content { + padding: 16px 24px; + + .ConfigureServer__card-title { + display: none; + } + + .ConfigureServer__card-form { + .ConfigureServer__card-form-input { + margin-top: 0; + background-color: var(--center-channel-bg); + } + } + } + } + } + } + + .ConfigureServer-inverted { + .ConfigureServer__body .ConfigureServer__content .ConfigureServer__card { + .ConfigureServer__card-content .ConfigureServer__card-form { + .ConfigureServer__card-form-input { + background-color: var(--denim-sidebar-header-bg); + } + + .disabled .ConfigureServer__card-form-input { + background-color: rgba(var(--denim-sidebar-header-bg-rgb), 0.54); + } + } + } + } +} + +@media screen and (max-width: 699px) { + .ConfigureServer { + .ConfigureServer__body { + margin: auto 0; + + .ConfigureServer__content { + min-width: 375px; + + .ConfigureServer__card { + width: 100%; + } + + .ConfigureServer__message { + width: auto; + align-self: flex-start; + padding: 24px; + + .ConfigureServer__message-title { + max-width: 271px; + padding-right: 0; + font-size: 45px; + line-height: 56px; + } + } + } + } + } +} diff --git a/src/renderer/css/components/Input.scss b/src/renderer/css/components/Input.scss new file mode 100644 index 00000000..873d4f5d --- /dev/null +++ b/src/renderer/css/components/Input.scss @@ -0,0 +1,254 @@ +@import url("../_css_variables.scss"); +@import '~@mattermost/compass-icons/css/compass-icons.css'; + +.error { + color: var(--error-text); +} + +.info { + color: var(--sidebar-text-active-border); +} + +.warning { + color: var(--away-indicator-dark); +} + +.success { + color: var(--online-indicator); +} + +.Input_container { + width: 100%; + + .Input { + position: relative; + z-index: 2; + left: 0; + width: 100%; + padding: 0; + border: 0; + border-radius: 4px; + box-shadow: none; + color: var(--center-channel-color); + background-color: unset; + font-size: 14px; + line-height: 20px; + outline: 0; + + &::placeholder { + color: rgba(var(--center-channel-text-rgb), 0.64); + } + + &.Input__focus { + box-shadow: none; + } + + &.large { + height: 42px; + font-size: 16px; + line-height: 24px; + } + } + + .Input_wrapper { + display: flex; + flex: 1; + padding: 0 16px; + margin: 2px 0; + color: rgba(var(--center-channel-color-rgb), 0.56); + font-size: 14px; + line-height: 20px; + } + + .Input_limit-exceeded { + align-self: center; + margin-left: 16px; + color: var(--error-text); + font-size: 14px; + line-height: 20px; + } + + .Input-border-error { + border-color: var(--error-text); + } + + .Input___error, + .Input___customMessage, + .Input___info { + margin-top: 5px; + font-size: 12px; + line-height: 16px; + text-align: left; + + i { + height: 14px; + align-self: baseline; + margin-right: 7px; + font-size: 14px; + + &::before { + margin: 0; + } + } + } + + .Input___error { + @extend .error; + + display: flex; + } + + .Input___warning { + @extend .warning; + + display: flex; + } + + .Input___success { + @extend .success; + + display: flex; + } + + .Input___info { + color: rgba(var(--center-channel-color-rgb), 0.56); + + > span.valid { + margin-left: 7px; + } + } + + .Input_fieldset { + position: relative; + z-index: 3; + display: flex; + width: 100%; + box-sizing: border-box; + padding: 0 1px; + border: 1px solid rgba(var(--center-channel-color-rgb), 0.16); + border-radius: 4px; + color: rgba(var(--secondary-blue-rgb), 0.4); + + &:hover { + border-color: rgba(var(--center-channel-color-rgb), 0.48); + } + + &:focus-within { + border-color: var(--button-bg); + box-shadow: inset 0 0 0 1px var(--button-bg); + color: var(--button-bg); + + .Input_legend { + color: var(--button-bg); + } + } + } + + .Input_legend { + position: absolute; + z-index: 4; + top: -8px; + display: flex; + width: auto; + padding: 0 4px; + border: none; + margin-left: 12px; + background-color: var(--center-channel-bg); + color: rgba(var(--center-channel-color-rgb), 0.64); + font-size: 10px; + line-height: 14px; + opacity: 0; + transform: translateY(8px); + transition-duration: 0.15s; + transition-property: opacity, transform; + transition-timing-function: ease-in-out; + white-space: nowrap; + } + + .Input_legend___focus { + padding: 0 4px; + opacity: 1; + transform: translateY(0); + } + + &.disabled { + .Input_fieldset { + border-color: rgba(var(--center-channel-color-rgb), 0.16); + background-color: rgba(var(--center-channel-color-rgb), 0.04); + } + } +} + +.Input_container-inverted { + .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: var(--denim-sidebar-header-bg); + border: 1px solid rgba(var(--onyx-center-channel-text-rgb), 0.16); + + &:hover { + border-color: rgba(var(--onyx-center-channel-text-rgb), 0.48); + } + + &:focus-within { + border-color: var(--onyx-center-channel-text-rgb); + box-shadow: inset 0 0 0 1px var(--onyx-center-channel-text-rgb); + color: var(--button-color); + + .Input_legend { + color: var(--button-color); + } + } + } + + .Input_legend { + background-color: var(--denim-sidebar-header-bg); + color: rgba(var(--button-color-rgb), 0.64); + } + + &.disabled { + .Input_fieldset { + background: rgba(var(--button-color-rgb), 0.08); + } + } +} + +.Input_container { + .Input_fieldset___error { + border-color: var(--error-text); + color: var(--error-text); + + &:focus-within { + border-color: var(--error-text); + box-shadow: inset 0 0 0 1px var(--error-text); + color: var(--error-text); + + .Input_legend { + color: var(--error-text); + } + } + + &:hover { + border-color: var(--error-text); + } + + .Input_legend { + color: var(--error-text); + } + } +} diff --git a/src/renderer/css/components/LoadingSpinner.scss b/src/renderer/css/components/LoadingSpinner.scss new file mode 100644 index 00000000..a8ce689f --- /dev/null +++ b/src/renderer/css/components/LoadingSpinner.scss @@ -0,0 +1,36 @@ +@import url("../fonts.css"); + +@keyframes spin-fw { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(359deg); + } +} + +.LoadingSpinner { + .spinner { + margin-right: 3px; + animation: spin-fw 1s infinite steps(8); + width: 1.28571429em; + text-align: center; + display: inline-block; + font-family: 'FontAwesome'; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + + &::before { + content: '\f110'; + display: inline-block; + box-sizing: border-box; + } + } + + &.with-text { + .spinner { + margin-right: 5px; + } + } +} diff --git a/src/renderer/css/components/WelcomeScreen.scss b/src/renderer/css/components/WelcomeScreen.scss index c97eed32..3a96d077 100644 --- a/src/renderer/css/components/WelcomeScreen.scss +++ b/src/renderer/css/components/WelcomeScreen.scss @@ -1,3 +1,5 @@ +@import '../_mixins.scss'; + .WelcomeScreen { flex-direction: column; z-index: 20; @@ -25,6 +27,13 @@ margin-top: 22px; } } + + &.outToLeft { + @include outToLeft(40); + + animation: outToLeft40 0.4s ease-in-out; + opacity: 0; + } } .WelcomeScreen__footer { diff --git a/src/renderer/css/fonts.css b/src/renderer/css/fonts.css index 3a709ec0..90bf16fd 100644 --- a/src/renderer/css/fonts.css +++ b/src/renderer/css/fonts.css @@ -81,3 +81,10 @@ 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'); +} diff --git a/src/renderer/modals/welcomeScreen/welcomeScreen.tsx b/src/renderer/modals/welcomeScreen/welcomeScreen.tsx index ef4354fc..37cd4916 100644 --- a/src/renderer/modals/welcomeScreen/welcomeScreen.tsx +++ b/src/renderer/modals/welcomeScreen/welcomeScreen.tsx @@ -4,6 +4,7 @@ import React, {useEffect, useState} from 'react'; import ReactDOM from 'react-dom'; +import {TeamWithIndex} from 'types/config'; import {ModalMessage} from 'types/modals'; import { @@ -11,22 +12,37 @@ import { GET_MODAL_UNCLOSEABLE, GET_DARK_MODE, DARK_MODE_CHANGE, + MODAL_INFO, } from 'common/communication'; import IntlProvider from 'renderer/intl_provider'; import WelcomeScreen from '../../components/WelcomeScreen'; +import ConfigureServer from '../../components/ConfigureServer'; import 'bootstrap/dist/css/bootstrap.min.css'; -const onGetStarted = () => { - window.postMessage({type: MODAL_RESULT}, window.location.href); +const MOBILE_SCREEN_WIDTH = 1200; + +const onConnect = (data: TeamWithIndex) => { + window.postMessage({type: MODAL_RESULT, data}, window.location.href); }; const WelcomeScreenModalWrapper = () => { const [darkMode, setDarkMode] = useState(false); + const [getStarted, setGetStarted] = useState(false); + const [mobileView, setMobileView] = useState(false); + const [currentTeams, setCurrentTeams] = useState([]); + + const handleWindowResize = () => { + setMobileView(window.innerWidth < MOBILE_SCREEN_WIDTH); + }; useEffect(() => { window.postMessage({type: GET_MODAL_UNCLOSEABLE}, window.location.href); window.postMessage({type: GET_DARK_MODE}, window.location.href); + + handleWindowResize(); + + window.addEventListener('resize', handleWindowResize); window.addEventListener('message', handleMessageEvent); return () => { @@ -34,23 +50,43 @@ const WelcomeScreenModalWrapper = () => { }; }, []); - const handleMessageEvent = (event: {data: ModalMessage}) => { - if (event.data.type === DARK_MODE_CHANGE) { - setDarkMode(event.data.data); + const handleMessageEvent = (event: {data: ModalMessage}) => { + switch (event.data.type) { + case DARK_MODE_CHANGE: + setDarkMode(event.data.data as boolean); + break; + case MODAL_INFO: + setCurrentTeams(event.data.data as TeamWithIndex[]); + break; + default: + break; } }; + const onGetStarted = () => { + setGetStarted(true); + }; + return ( - + {getStarted ? ( + + ) : ( + + )} ); }; -const start = async () => { +const start = () => { ReactDOM.render( , document.getElementById('app'),