[MM-23067] Browser View (#1514)

* Browser-view: initial architectural changes + webpack (#1358)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* CR changes

* Browserview settings window (#1362)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* [BrowserView] renderer (#1378)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* remove log statements

* Bv menus (#1387)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* package.json

* fix merge results

* fix package-lock

* remove debug statements

* address CR requests

* [MM-22691][Browserview] fix tray icon (#1403)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* fix webpack adding images to /dist so tray can render them

* wait for config, fix menutray calls

* remove .gitattributes from being tracked

* remove unused reject

* remove logs

* Update webpack.config.renderer.js

Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>

Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>

* Browserview URLHover (#1393)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* wip

* wip urlview

* wip

* urlview when hovering on a link

* change how to detect when the mouse hovers

* [BrowserView] remove remote usage, fix menus and window buttons in Win (#1418)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* fix webpack adding images to /dist so tray can render them

* wait for config, fix menutray calls

* remove .gitattributes from being tracked

* remove unused reject

* remove logs

* Update webpack.config.renderer.js

Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>

* fix three dot menu

* remove most remote usage, fix window buttons in Windows

Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>

* fix different errors when loading config (#1420)

* [BrowserView] Native modules & registry access (#1417)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* fix webpack adding images to /dist so tray can render them

* wait for config, fix menutray calls

* remove .gitattributes from being tracked

* restart-working native modules

* setup env variables for installing native modules

* [browserview] Electron notifications (#1411)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* wip

* move viewmanager into windowmanager

* working notifications

* remove logs, switch tab on notification click

* download notifications

* fix tray

* fix menu switch server

* fix error

* [MM-23078] TabBar fixes for BrowserView (#1423)

* [MM-23078] TabBar fixes for BrowserView

* Removing unnecessary logging

* [Browserview] 4.6 and 4.7 PRs (#1424)

* [MM-28620] allow navigating links to admin_console #1374

* [MM-25789] - Update default settings for new installations #1376

* [MM-27332] show window at autolaunch #1379

* Update NOTICE.txt (#1385)

* Update NOTICE.txt

* Update NOTICE.txt

* Update NOTICE.txt

* convert to markdown

* md linting

* Update NOTICE.md

* Revert "Update NOTICE.md"

This reverts commit 9381fca895c0677bcad1cf1c1071ca88afd6f486.

* Revert "md linting"

This reverts commit e7a68f120109d47b9849cf816d4fef79483ad22f.

* Revert "convert to markdown"

This reverts commit 1e7ed8a67c9c98cd0d0f3ff6cdc70782effb143d.

* add missing licenses to joi and jq

* Remove devDependencies

Co-authored-by: Guillermo Vaya <guivaya@gmail.com>

* Notification sounds, also added tab name to notification title

* [MM-22013] - Allow users to specify default download locations #1383

* [MM-21835] Use URL instead of the url library #1384

* remove debug console.log statements

Co-authored-by: Amy Blais <amy_blais@hotmail.com>

* [MM-31266] fix access url when it's not a mm server (#1431)

* [MM-31224] fix reloading servers and other tab issues (#1434)

* [MM-31224] fix reloading servers and other tab issues

* reload if url changes

* Change the dev server port to 9001 to avoid conflict with mattermost-minio (#1437)

* remove dev_web_server (#1438)

* [MM-31225][MM-31217][MM-31219][Browserview] fix linux compilation + other fixes (#1433)

* fix linux errors

* remove registry, remove env_vars

* devtools in separate window, prevent config errors

* fix registry path

* move dist to root when packaging

* make devtools dettached to avoid browserview

* remove unneeded comment

* use reject in case of registry failure

* fix handling results

* fix application menu

* make linter happy

* fix missing key on apt-get (#1440) (#1442)

see https://github.com/electron-userland/electron-builder/issues/5485#issuecomment-749244332

* [MM-31221][BrowserView] first modal: adding a server while in a server view (#1400)

* reorder code to support webpack

* start backend changes

* remove simple-spellchecker

* wip

* first browserview run

* settings window routing

* wip

* back to webpack

* working build

* back to using electron-builder

* fix linting

* linting errors missed

* back to just 1 config

* missing changes

* refactor and have the settings in its own page

* reminder to restore disabling window.eval

* wip

* wip

* remove old webpack generated files

* add assets files

* more remove files and fix localurls

* wip settings, needs fixing saving prefs

* remove linting errors

* remove settings as a modal

* fix linting

* remove view from window on destroy

* restore visibility if reloaded

* debug log

* look for closed windows, remove managers from settings as it is a full window

* restore view on configuration save

* linting and debug

* remove debug message

* make eslint be aware of webpack aliases

* some extra disable lines

* move badge management to main

* remove unneded import

* fixing errors

* wip

* back to having tabs

* switch tab working

* wip

* wip

* wip

* fix quitting error

* back to a working config

* configure retries

* add darkmode

* wip

* add error/loading screens

* fix settings while removing remote usage

* wip

* fix lint, get preload to load

* remove unused import

* wip

* menus initially working as they should

* update deps, show context menu

* wip

* wip

* wip

* fix forward/back menu

* fix server menu

* allow navigating to external urls in the browser

* add defaults to menu

* fix logic

* set default options

* remove logs

* wip

* wip

* wip urlview

* wip

* urlview when hovering on a link

* wip

* wip

* first working modal

* fix config loading

* upgrade electron to 10.1.5

* esc exits modals

* first modal

* add env variables for settings and modals devtools

* adress CSS review comments

* Address review comments

* fix dist in prod

* fix preload path on build

* [MM-31987] Allow camera use for jitsi (#1443) (#1450)

* [MM-31987] allow camera use for jitsi

* update message for access

* [MM-31261] Use manual resizing of BrowserViews on resize, maximize and full-screen (#1449)

* [MM-31261] Use manual resizing of BrowserViews on resize, maximize and full-screen

* Update src/main/windows/windowManager.js

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* add own branch for testing (#1448)

* add own branch for testing

* remove signing for windows

* add message to channel

* Bv pipeline elisabeth (#1452)

* Add parameter and remove schedule

* Add jq

* Fix adding jq

* Fix adding jq

* Fix adding jq

* fix quotes

* upload as JSON

* use previous, parse json

* fixes

* use json

Co-authored-by: Elisabeth Kulzer <elikul@elikul.de>

* [MM-30144][MM-30145][MM-30146][MM-30147] Migrate auth and certificate modals to BrowserView (#1445)

* WIP

* WIP

* WIP

* WIP

* WIP

* [MM-30144][MM-30145] Migrate LoginModal and PermissionModal to BrowserView

* [MM-30146][MM-30147] Migrate certificate modals to BrowserView

* Fixed transparency on the bootstrap modals

* PR feedback

* Added better error reporting in case the modal promise fails

* [MM-31233] Reverse maximize logic typo (#1454)

* [browser view] MM-32277 bump version, exe, cache errrors (#1456)

* bump version

* enable msi and remove src/package*

* ensure variable exists

* remove cleanCache script

* default expansion for env variable

* add commit version, missing package-lock.json

* remove duplicated command

* [MM-31467] Move protocol handling over from original MattermostView into web contents handler (#1453)

* WIP

* WIP

* [MM-31467] Move protocol handling over from original MattermostView into web contents handler

* Remove log statement

* [MM-32392] prevent crash when checking a URL (#1457)

* [MM-31215][MM-31387] Fixes for bad tab navigation and dragging (#1461)

* [MM-31387] Send to renderer on clicking server from settings window

* Use different event name for sending switch server info to renderer

* Have the viewManager let the renderer know when the tab has changed

* Couple more fixes around tabs

* Simplify URL compare logic

* [MM-31650] Restore focus to active server on modal and settings window closure + other fixes (#1455)

* [MM-31650] Focus active server on settings window and modal closure

* Disable tabs when a modal is open

* Revert to using original NewTeamModal component

* fix resize (#1462)

* [MM-32424] fix server devtools being hidden by browserview (#1459)

* [MM-32424] fix server devtools being hidden by browserview

* reverse logic

* [MM-20227][MM-31388] move to roles and fix focus (#1463)

* [MM-31570] update mentions/unreads/session on jewel, tray and dock (#1460)

* [MM-32333] Open public links in the user's default browser (#1468)

* [MM-32333] Open public links in the user's default browser

* Removed commented code

* [MM-31232] fix urlview present with no content (#1467)

* [MM-31343] Migrate Finder to BrowserView (#1466)

* WIP

* WIP

* WIP

* [MM-31343] Migrate Finder to BrowserView

* PR feedback

* Removing reference to this in non-class file

* use electron to handle spellchecking (#1469)

* [MM-32382] Use resize event instead of will-resize for monitoring size of BV (#1470)

* [MM-32570] Use OpenSans as the font for the URL preview modal (#1471)

* [MM-32570] Use OpenSans as the font for the URL preview modal

* Don't use bootstrap

* Fix draw badge (#1477)

* use canvas from window

* fix errors

* fix errors

* safer code injection

* [MM-31554] Add listener for config synchronization on the settings window (#1473)

* [MM-31554] Add listener for config synchronization on the settings window

* Synchronize the config if updated from outside the settings window

* [MM-28541] restore deeplinking (#1475)

* handle deeplinking

* fix app handling deeplinking

* remove outdated comment

* address review comments

* MM-32765_prevent crash on checking unread state (#1479)

* MM-31383 make no the default when asking to add a protocol (#1481)

* [MM-31340] Resize browser view and show back button when on non-team URL (#1472)

* WIP

* [MM-31340] Resize browser view and show back button when on non-team URL

* Fixed issue where switching tabs and resizing hides the back button

* Add error checking around going back in history

* [MM-31399] Use webapp ESLint config in desktop app and resolve inconsistencies (#1482)

* Import webapp eslint and update packages

* FIrst pass with new ruleset

* Allow setState

* Fix rule for tests

* Comment out skippeed tests, removed some TODOs and fixed some warnings

* Remove errors from MainPage

* Use indenting profile from webapp

* Update editorconfig for new indenting

* Fix indenting for class properties

* Only disable no-console for renderer process and scripts

* Remove rule overrides and changes

* Fix merge issues

* PR feedback and fixed a bad merge

* [MM-25122] Use modded version of winreg that supports UTF-8 (#1488)

* fix appicon path resolution (#1484)

* [MM-33141] Fixed use of bad context in TeamList (#1487)

* [MM-33141] Fixed use of bad context in TeamList

* Refactor to pull the functions out

* Remove unnecessary props

* [MM-25355] Throttle notifications for Windows by channel id (#1486)

* [MM-25355] Throttle notifications for Windows

* Use teamId as well to key the notifications

* Merge'd

* Use Map instead of Set

* [MM-33050] move webcontent events out of main (#1489)

* wip

* wip

* fix webcontent events, move views to its own folder

* [MM-33238] Check for admin URL when toggling back bar (#1495)

* [MM-31342] fix "save image as" context menu crash (#1490)

* [MM-33231] update jewel on new mentions/when read (#1493)

* [MM-33231] update state properly for a purecomponent

* remove unneeded comment

* [MM-33032] Use `hidden` titleBarStyle value to fix macOS Catalina click issue (#1496)

* [MM-32809] Remove Toggle Dark Mode menu item for Windows, enable toggling on Linux (#1494)

* [MM-32809] Remove Toggle Dark Mode menu item for Windows

* Just check for !win32 and !darwin

* Enable correct dark mode functionality on non-macOS/non-Windows machines

* [MM-33334] Restore keyboard shortcuts for menu items moved to roles (#1499)

* [MM-33434] Upgrade to Electron v11, some other dependency upgrades (#1501)

* [MM-33434] Upgrade to Electron v11, some other dependency upgrades

* Missed a version change

* context menu fix

* Forgot to remove a log statement

* Added resized for redundancy and upgraded to spectron 13

* Don't need resized

* [MM-33542] Trigger finder cleanup on pressing close or Escape (#1502)

* [MM-33542] Clear the Finder selection when closing the finder

* Remove listener on close as well

* Run close() on escape as well

* [MM-33607] Remove old badge code, update unreads code (#1503)

* [MM-33607] Remove old badge code, update unreads code

* Fix 2 random lint errors

* [MM-33247] Have the app handle links to other teams as a deep link (#1498)

* [MM-33373] Trigger the smaller font for 99+ mentions (#1507)

* [MM-32805] Merge master, migrate LoadingScreen to BrowserView (#1504)

* [MM-467] Notification sounds (#1351)

* Custom sounds

* Trying new version

* Trying new version

* Some fixes

* Rollback version change

* Allow native sound

* Increase version

* Playing custom sounds :)

* Fix var name

* Fix

* Update src/browser/js/notification.js

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* Update src/browser/js/notification.js

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* Update src/browser/js/notification.js

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* Several suggestions

* Update src/browser/js/notification.js

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* Restore of version

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* Clean caches on depcheck failure (#1369)

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>

* [MM-28595] Open team links within the app (#1373)

* [MM-25789] - Update default settings for new installations (#1376)

* [MM-25789] - Update default settings for new installations

* Update src/main.js

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>

* Update src/main.js

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>

* Fix linter

Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MacBook-Pro-2.local>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>

* add Russian language in the list available for spellcheck (#1375)

* [MM-28620] allow navigating links to admin_console (#1374)

* [MM-28620] allow navigating links to admin_console

* Fix when there is not a server associated

* [MM-27332] show window at autolaunch (#1379)

* Bump to version 4.7.0-develop

* Update NOTICE.txt (#1385)

* Update NOTICE.txt

* Update NOTICE.txt

* Update NOTICE.txt

* convert to markdown

* md linting

* Update NOTICE.md

* Revert "Update NOTICE.md"

This reverts commit 9381fca895c0677bcad1cf1c1071ca88afd6f486.

* Revert "md linting"

This reverts commit e7a68f120109d47b9849cf816d4fef79483ad22f.

* Revert "convert to markdown"

This reverts commit 1e7ed8a67c9c98cd0d0f3ff6cdc70782effb143d.

* add missing licenses to joi and jq

* Remove devDependencies

Co-authored-by: Guillermo Vaya <guivaya@gmail.com>

* [MM-9922] Hide tooltip for internal links (channels, timestamps, etc.) (#1386)

* Hide tooltip for internal links (channels, timestamps, etc.)

* Only hide tooltip for internal links on the *current* team

* feat(spellcheck): add Ukrainian language for spellcheck (#1382)

* [MM-29677] fix download complete notification not appearing (#1388)

* fix soundname not existing (#1390)

* [MM-29921] fix custom sound not playing when receiving a notification (#1396)

* [MM-29921] fix sound notification

* remove logs

* Update release-process.md (#1394)

* [MM-22013] - Allow users to specify default download locations (#1383)

* [MM-22013] - Allow users to specify default download locations

* PR comments

* Add proper config prop

* Update src/browser/components/SettingsPage.jsx

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>

* Remove string ref

* Fix styling

* Update styling

* Disable input

* Add variable for windows

* Prevent dialog from opening twice

Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MBP-2.fritz.box>
Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MacBook-Pro-2.local>
Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>

* [MM-21835] Use URL instead of the url library (#1384)

Additionally, migrate all of the URL related helper functions
from `src/utils/utils.js` to the new `src/utils/url.js` file
and migrate tests.

Issue MM-21835
Fixes #1206

* Merge Powershell files together and remove AppVeyor related code

* Ensure nodejs deps are met before running script argument directly

* [MM-22810] Update loading screen with new design & animation (#1409)

* Update loading screen with new design & animation

* add prop back in

* adjust z-index for tests

* tweaks to pass tests

* address offline feedback

- shrink initial logo size
- introduce a slight delay before fading loading spinner out
- fix horizontal scrollbar showing on load screen

* add missing css variable

* no need to remove loading icon

* Apply suggestions from code review

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>

* Move LoadingScreen.jsx to file-only component

* Rename prop for better clarity

* Default prop to none and check when needed

* Update import paths

* Add ESDocs and remove unecessary conditional

* Forgot to remove the eslint override

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>

* [MM-22960] - Keep desktop app pinned to taskb bar when the app upgrades (#1397)

Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MacBook-Pro-2.local>

* Bump highlight.js from 9.18.1 to 9.18.5 (#1421)

Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 9.18.1 to 9.18.5.
- [Release notes](https://github.com/highlightjs/highlight.js/releases)
- [Changelog](https://github.com/highlightjs/highlight.js/blob/9.18.5/CHANGES.md)
- [Commits](https://github.com/highlightjs/highlight.js/compare/9.18.1...9.18.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump ini from 1.3.5 to 1.3.7 (#1427)

Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix missing key on apt-get (#1440)

see https://github.com/electron-userland/electron-builder/issues/5485#issuecomment-749244332

* [MM-31987] Allow camera use for jitsi (#1443)

* [MM-31987] allow camera use for jitsi

* update message for access

* Created codeql analysis (#1441)

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>

* [MM-31626] bypass gitlab browser-check for oauth login (#1439)

* MM-31626 make User Agent configurable by user

* add info

* remove chrome from UA for gitlab.com

* remove previous solution

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>

* Add Swedish sv-SE (already in simple-spellchecker) (#1483)

* Add Swedish sv-SE (already in simple-spellchecker)

* Remove spaces in empty lines

* Add some sv-SE test for spellchecker

Co-authored-by: Peter Johansson <peter.johansson@havochvatten.se>

* Add loading screen, fix reload

* WIP

* Migrate LoadingScreen to BrowserView

* Lint fixes

* Removed gitlab fix code, also returning null is bad apparently

* Fix reload logic

Co-authored-by: Rodrigo Villablanca <villa061004@gmail.com>
Co-authored-by: Guillermo Vayá <guivaya@gmail.com>
Co-authored-by: Juho Nurminen <juho.nurminen@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>
Co-authored-by: Nev Angelova <nevy.angelova@gmail.com>
Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MacBook-Pro-2.local>
Co-authored-by: Eugeny Fomin <github.com@jeka.ru>
Co-authored-by: Amy Blais <amy_blais@hotmail.com>
Co-authored-by: Nathan Bolender <nathan@nathanbolender.com>
Co-authored-by: Dmitriy Danilov <daniloff200@gmail.com>
Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MBP-2.fritz.box>
Co-authored-by: FalseHonesty <skipboman0@gmail.com>
Co-authored-by: William Gathoye <william@gathoye.be>
Co-authored-by: Dean Whillier <deanwhillier@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rohitesh Gupta <srkg.gupta@gmail.com>
Co-authored-by: petermcj <petermcj@gmail.com>
Co-authored-by: Peter Johansson <peter.johansson@havochvatten.se>

* [MM-33668] Restore tests to browser-view branch (#1506)

* happy eslint

* wip

* wip

* remove aliases

* almost working tests

* green tests

* Revert "remove aliases"

This reverts commit 803d3695538197407b45e0d8d30dc429b259b7f3.

* add unit test, reconfigure package scripts, make test pass

* [MM-33542] Trigger finder cleanup on pressing close or Escape (#1502)

* [MM-33542] Clear the Finder selection when closing the finder

* Remove listener on close as well

* Run close() on escape as well

* [MM-33607] Remove old badge code, update unreads code (#1503)

* [MM-33607] Remove old badge code, update unreads code

* Fix 2 random lint errors

* fix script naming in circle

* fix check deps

* attempt to fix dependency-check download

* remove check-deps step

Co-authored-by: = <=>
Co-authored-by: Devin Binnie <52460000+devinbinnie@users.noreply.github.com>

* Cleanup of BrowserView migration, some bug fixes (#1509)

* 1st round of cleanup

* 2nd round of cleanup

* Set constant for reload-config

* Cleaned up some TODOs

* store daily build to S3 (#1508)

* store daily build to S3

* missing colon

* fix paths

* try to keep folders

* remove unneeded step

* change from arn to bucket name

* keep organization consistent

* fix indentation

* fix indentation x2

Co-authored-by: = <=>

* MM-33551 keep tray state between themes (#1511)

Co-authored-by: = <=>

* Set to version v4.7

Co-authored-by: Guillermo Vayá <guillermo.vaya@mattermost.com>
Co-authored-by: Amy Blais <amy_blais@hotmail.com>
Co-authored-by: Guillermo Vayá <guivaya@gmail.com>
Co-authored-by: Elisabeth Kulzer <elikul@elikul.de>
Co-authored-by: Rodrigo Villablanca <villa061004@gmail.com>
Co-authored-by: Juho Nurminen <juho.nurminen@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Nev Angelova <nevy.angelova@gmail.com>
Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MacBook-Pro-2.local>
Co-authored-by: Eugeny Fomin <github.com@jeka.ru>
Co-authored-by: Nathan Bolender <nathan@nathanbolender.com>
Co-authored-by: Dmitriy Danilov <daniloff200@gmail.com>
Co-authored-by: Nevyana Angelova <nevyangelova@Nevyanas-MBP-2.fritz.box>
Co-authored-by: FalseHonesty <skipboman0@gmail.com>
Co-authored-by: William Gathoye <william@gathoye.be>
Co-authored-by: Dean Whillier <deanwhillier@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rohitesh Gupta <srkg.gupta@gmail.com>
Co-authored-by: petermcj <petermcj@gmail.com>
Co-authored-by: Peter Johansson <peter.johansson@havochvatten.se>
This commit is contained in:
Devin Binnie
2021-03-18 10:51:53 -04:00
committed by GitHub
parent 9551d6628c
commit e76e0dc0a1
209 changed files with 17905 additions and 13921 deletions

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import PropTypes from 'prop-types';
import {Alert} from 'react-bootstrap';
const baseClassName = 'AutoSaveIndicator';
const leaveClassName = `${baseClassName}-Leave`;
const SAVING_STATE_SAVING = 'saving';
const SAVING_STATE_SAVED = 'saved';
const SAVING_STATE_ERROR = 'error';
const SAVING_STATE_DONE = 'done';
function getClassNameAndMessage(savingState, errorMessage) {
switch (savingState) {
case SAVING_STATE_SAVING:
return {className: baseClassName, message: 'Saving...'};
case SAVING_STATE_SAVED:
return {className: baseClassName, message: 'Saved'};
case SAVING_STATE_ERROR:
return {className: `${baseClassName}`, message: errorMessage};
case SAVING_STATE_DONE:
return {className: `${baseClassName} ${leaveClassName}`, message: 'Saved'};
default:
return {className: `${baseClassName} ${leaveClassName}`, message: ''};
}
}
export default function AutoSaveIndicator(props) {
const {savingState, errorMessage, ...rest} = props;
const {className, message} = getClassNameAndMessage(savingState, errorMessage);
return (
<Alert
className={className}
{...rest}
bsStyle={savingState === 'error' ? 'danger' : 'info'}
>
{message}
</Alert>
);
}
AutoSaveIndicator.propTypes = {
savingState: PropTypes.string.isRequired,
errorMessage: PropTypes.string,
};
Object.assign(AutoSaveIndicator, {
SAVING_STATE_SAVING,
SAVING_STATE_SAVED,
SAVING_STATE_ERROR,
SAVING_STATE_DONE,
});

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import {storiesOf} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import {Button, ButtonToolbar} from 'react-bootstrap';
storiesOf('Button', module).
add('bsStyle', () => (
<ButtonToolbar>
<Button onClick={action('clicked default')}>{'Default'}</Button>
<Button
onClick={action('clicked primary')}
bsStyle='primary'
>{'Primary'}</Button>
<Button
onClick={action('clicked danger')}
bsStyle='danger'
>{'Danger'}</Button>
<Button
onClick={action('clicked link')}
bsStyle='link'
>{'Link'}</Button>
</ButtonToolbar>
));

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import PropTypes from 'prop-types';
import {Button, Modal} from 'react-bootstrap';
export default function DestructiveConfirmationModal(props) {
const {
title,
body,
acceptLabel,
cancelLabel,
onAccept,
onCancel,
...rest} = props;
return (
<Modal {...rest}>
<Modal.Header closeButton={true}>
<Modal.Title>{title}</Modal.Title>
</Modal.Header>
{body}
<Modal.Footer>
<Button
bsStyle='link'
onClick={onCancel}
>{cancelLabel}</Button>
<Button
bsStyle='danger'
onClick={onAccept}
>{acceptLabel}</Button>
</Modal.Footer>
</Modal>
);
}
DestructiveConfirmationModal.propTypes = {
title: PropTypes.string.isRequired,
body: PropTypes.node.isRequired,
acceptLabel: PropTypes.string.isRequired,
cancelLabel: PropTypes.string.isRequired,
onAccept: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,88 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
// ErrorCode: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h
import React from 'react';
import PropTypes from 'prop-types';
import {Grid, Row, Col} from 'react-bootstrap';
export default function ErrorView(props) {
const classNames = ['container', 'ErrorView'];
if (!props.active) {
classNames.push('ErrorView-hidden');
}
return (
<Grid
id={props.id}
bsClass={classNames.join(' ')}
>
<div className='ErrorView-table'>
<div className='ErrorView-cell'>
<Row>
<Col
xs={0}
sm={1}
md={1}
lg={2}
/>
<Col
xs={12}
sm={10}
md={10}
lg={8}
>
<h2>{`Cannot connect to ${props.appName}`}</h2>
<hr/>
<p>{`We're having trouble connecting to ${props.appName}. If refreshing this page (Ctrl+R or Command+R) does not work please verify that:`}</p>
<br/>
<ul className='ErrorView-bullets' >
<li>{'Your computer is connected to the internet.'}</li>
<li>{`The ${props.appName} URL `}
<a
//onClick={handleClick}
href={props.url}
target='_blank'
rel='noreferrer'
>
{props.url}
</a>{' is correct.'}</li>
<li>{'You can reach '}
<a
// onClick={handleClick}
href={props.url}
target='_blank'
rel='noreferrer'
>
{props.errorInfo.validatedURL}
</a>{' from a browser window.'}</li>
</ul>
<br/>
<div className='ErrorView-techInfo'>
{props.errorInfo}
</div>
</Col>
<Col
xs={0}
sm={1}
md={1}
lg={2}
/>
</Row>
</div>
</div>
</Grid>
);
}
ErrorView.propTypes = {
errorInfo: PropTypes.string,
url: PropTypes.string,
id: PropTypes.string,
active: PropTypes.bool,
appName: PropTypes.string,
};

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {Row, Button} from 'react-bootstrap';
export default class ExtraBar extends React.PureComponent {
handleBack = () => {
if (this.props.goBack) {
this.props.goBack();
}
}
render() {
let barClass = 'clear-mode';
if (!this.props.show) {
barClass = 'hidden';
} else if (this.props.darkMode) {
barClass = 'dark-mode';
}
return (
<Row
id={'extra-bar'}
className={barClass}
>
<div
className={'container-fluid'}
onClick={this.handleBack}
>
<Button
bsStyle={'link'}
bsSize={'xsmall'}
>
<span className={'backIcon fa fa-1x fa-angle-left'}/>
<span className={'backLabel'}>
{'Back'}
</span>
</Button>
</div>
</Row>
);
}
}
ExtraBar.propTypes = {
darkMode: PropTypes.bool,
goBack: PropTypes.func,
show: PropTypes.bool,
};

View File

@@ -0,0 +1,93 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import useAnimationEnd from '../../hooks/useAnimationEnd.js';
import LoadingIcon from './LoadingIcon.jsx';
const LOADING_STATE = {
INITIALIZING: 'initializing', // animation graphics are hidden
LOADING: 'loading', // animation graphics fade in and animate
LOADED: 'loaded', // animation graphics fade out
COMPLETE: 'complete', // animation graphics are removed from the DOM
};
const ANIMATION_COMPLETION_DELAY = 500;
/**
* A function component for rendering the animated MM logo loading sequence
* @param {boolean} loading - Prop that indicates whether currently loading or not
* @param {boolean} darkMode - Prop that indicates if dark mode is enabled
* @param {function} onLoadingAnimationComplete - Callback function to update when internal loading animation is complete
*/
function LoadingAnimation({
loading = false,
darkMode = false,
onLoadAnimationComplete = null},
) {
const loadingIconContainerRef = React.useRef(null);
const [animationState, setAnimationState] = React.useState(LOADING_STATE.INITIALIZING);
const [loadingAnimationComplete, setLoadingAnimationComplete] = React.useState(false);
React.useEffect(() => {
if (loading) {
setAnimationState(LOADING_STATE.LOADING);
setLoadingAnimationComplete(false);
}
// in order for the logo animation to fully complete before fading out, the LOADED state is not set until
// both the external loaded prop changes back to false and the internal loading animation is complete
if (!loading && loadingAnimationComplete) {
setAnimationState(LOADING_STATE.LOADED);
}
}, [loading]);
React.useEffect(() => {
// in order for the logo animation to fully complete before fading out, the LOADED state is not set until
// both the external loaded prop goes back to false and the internal loading animation is complete
if (!loading && loadingAnimationComplete) {
setAnimationState(LOADING_STATE.LOADED);
}
}, [loadingAnimationComplete]);
// listen for end of the css logo animation sequence
useAnimationEnd(loadingIconContainerRef, () => {
setTimeout(() => {
setLoadingAnimationComplete(true);
}, ANIMATION_COMPLETION_DELAY);
}, 'LoadingAnimation__compass-shrink');
// listen for end of final css logo fade/shrink animation sequence
useAnimationEnd(loadingIconContainerRef, () => {
if (onLoadAnimationComplete) {
onLoadAnimationComplete();
}
setAnimationState(LOADING_STATE.COMPLETE);
}, 'LoadingAnimation__shrink');
return (
<div
ref={loadingIconContainerRef}
className={classNames('LoadingAnimation', {
'LoadingAnimation--darkMode': darkMode,
'LoadingAnimation--spinning': animationState !== LOADING_STATE.INITIALIZING && animationState !== LOADING_STATE.COMPLETE,
'LoadingAnimation--loading': animationState === LOADING_STATE.LOADING && animationState !== LOADING_STATE.COMPLETE,
'LoadingAnimation--loaded': animationState === LOADING_STATE.LOADED && animationState !== LOADING_STATE.COMPLETE,
})}
>
<LoadingIcon/>
</div>
);
}
LoadingAnimation.propTypes = {
loading: PropTypes.bool,
darkMode: PropTypes.bool,
onLoadAnimationComplete: PropTypes.func,
};
export default LoadingAnimation;

View File

@@ -0,0 +1,197 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
/**
* A function component for inlining SVG code for animation logo loader
*/
function LoadingAnimation() {
return (
<svg
width='104'
height='104'
viewBox='0 0 104 104'
xmlns='http://www.w3.org/2000/svg'
>
<defs>
<linearGradient
id='LoadingAnimation__spinner-gradient'
x1='0%'
y1='72px'
x2='0%'
y2='32px'
gradientUnits='userSpaceOnUse'
>
<stop
offset='0'
className='LoadingAnimation__spinner-gradient-color'
stopOpacity='1'
/>
<stop
offset='1'
className='LoadingAnimation__spinner-gradient-color'
stopOpacity='0'
/>
</linearGradient>
<mask id='LoadingAnimation__base-wipe-mask'>
<rect
x='0'
y='0'
width='104'
height='104'
fill='white'
/>
<g className='LoadingAnimation__compass-base-mask-container'>
<circle
className='LoadingAnimation__compass-base-mask'
r='27'
cx='52'
cy='52'
fill='white'
stroke='black'
strokeWidth='54'
/>
</g>
</mask>
<mask id='LoadingAnimation__base-mask'>
<rect
x='0'
y='0'
width='104'
height='104'
fill='white'
/>
<circle
r='37'
cx='54'
cy='46'
fill='black'
/>
<g className='LoadingAnimation__compass-needle-behind-mask'>
<g transform='translate(54,46)'>
<g transform='translate(-29, -61.3)'>
<path
d='M38.5984 0C45.476 1.07762 51.9794 3.28918 57.9108 6.43722V61.1566C57.9108 77.1373 44.9364 90.1119 28.9554 90.1119C12.9744 90.1119 0 77.1373 0 61.1566C0 55.3848 1.69443 50.0063 4.60763 45.4861L38.5984 0Z'
fill='black'
/>
</g>
</g>
</g>
<g className='LoadingAnimation__compass-needle-front-mask'>
<g transform='translate(54,46)'>
<g transform='translate(-29,-61.3)'>
<path
d='M38.5984 0C45.476 1.07762 51.9794 3.28918 57.9108 6.43722V61.1566C57.9108 77.1373 44.9364 90.1119 28.9554 90.1119C12.9744 90.1119 0 77.1373 0 61.1566C0 55.3848 1.69443 50.0063 4.60763 45.4861L38.5984 0Z'
fill='black'
/>
</g>
</g>
</g>
</mask>
<mask id='LoadingAnimation__spinner-left-half-mask'>
<rect
x='0'
y='0'
width='52'
height='104'
fill='white'
/>
<circle
className='LoadingAnimation__spinner-mask'
r='20'
cx='52'
cy='52'
fill='black'
/>
</mask>
<mask id='LoadingAnimation__spinner-right-half-mask'>
<rect
x='52'
y='0'
width='52'
height='104'
fill='white'
/>
<circle
className='LoadingAnimation__spinner-mask'
r='20'
cx='52'
cy='52'
fill='black'
/>
</mask>
<mask id='LoadingAnimation__spinner-wipe-mask'>
<rect
x='0'
y='0'
width='104'
height='104'
fill='white'
/>
<g className='LoadingAnimation__spinner-mask-container'>
<circle
className='LoadingAnimation__spinner-mask'
r='27'
cx='52'
cy='52'
fill='black'
stroke='white'
strokeWidth='54'
/>
</g>
</mask>
</defs>
<g
className='LoadingAnimation__spinner-container'
mask='url(#LoadingAnimation__spinner-wipe-mask)'
>
<g className='LoadingAnimation__spinner'>
<circle
r='25'
cx='52'
cy='52'
fill='currentColor'
mask='url(#LoadingAnimation__spinner-left-half-mask)'
/>
<circle
r='25'
cx='52'
cy='52'
fill='url(#LoadingAnimation__spinner-gradient)'
mask='url(#LoadingAnimation__spinner-right-half-mask)'
/>
</g>
</g>
<g className='LoadingAnimation__compass'>
<g
className='LoadingAnimation__compass-base-container'
mask='url(#LoadingAnimation__base-wipe-mask)'
>
<circle
className='LoadingAnimation__compass-base'
r='52'
cx='52'
cy='52'
fill='currentColor'
mask='url(#LoadingAnimation__base-mask)'
/>
</g>
<g className='LoadingAnimation__compass-needle-container'>
<g className='LoadingAnimation__compass-needle'>
<g transform='translate(54,46)'>
<g transform='translate(-15,-42)'>
<path
d='M29.9539 1.4977C29.9539 0.670968 29.2827 0 28.4562 0C27.9597 0 27.5192 0.242028 27.2468 0.614415C27.216 0.656555 27.1873 0.700359 27.1609 0.745666L3.66519 32.1191C1.38202 34.7479 0 38.1803 0 41.9355C0 50.207 6.70541 56.9124 14.977 56.9124C23.2485 56.9124 29.9539 50.207 29.9539 41.9355L29.9539 41.9013V1.50252C29.9539 1.50091 29.9539 1.49931 29.9539 1.4977Z'
fill='currentColor'
/>
</g>
</g>
</g>
</g>
</g>
</svg>
);
}
export default LoadingAnimation;

View File

@@ -0,0 +1,4 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export {default} from './LoadingAnimation.jsx';

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import useTransitionEnd from '../hooks/useTransitionEnd.js';
import LoadingAnimation from './LoadingAnimation';
/**
* A function component for rendering the desktop app loading screen
* @param {boolean} loading - Prop that indicates whether currently loading or not
* @param {boolean} darkMode - Prop that indicates if dark mode is enabled
* @param {() => void} onFadeOutComplete - Function to call when the loading animation is completely finished
*/
function LoadingScreen({loading = false, darkMode = false, onFadeOutComplete = () => null}) {
const loadingScreenRef = React.useRef(null);
const [loadingIsComplete, setLoadingIsComplete] = React.useState(true);
const [loadAnimationIsComplete, setLoadAnimationIsComplete] = React.useState(true);
const [fadeOutIsComplete, setFadeOutIsComplete] = React.useState(true);
React.useEffect(() => {
// reset internal state if loading restarts
if (loading) {
resetState();
} else {
setLoadingIsComplete(true);
}
}, [loading]);
function handleLoadAnimationComplete() {
setLoadAnimationIsComplete(true);
}
useTransitionEnd(loadingScreenRef, React.useCallback(() => {
setFadeOutIsComplete(true);
onFadeOutComplete();
}), ['opacity']);
function loadingInProgress() {
return !(loadingIsComplete && loadAnimationIsComplete && fadeOutIsComplete);
}
function resetState() {
setLoadingIsComplete(false);
setLoadAnimationIsComplete(false);
setFadeOutIsComplete(false);
}
const loadingScreen = (
<div
ref={loadingScreenRef}
className={classNames('LoadingScreen', {
'LoadingScreen--darkMode': darkMode,
'LoadingScreen--loaded': loadingIsComplete && loadAnimationIsComplete,
})}
>
<LoadingAnimation
loading={loading}
darkMode={darkMode}
onLoadAnimationComplete={handleLoadAnimationComplete}
/>
</div>
);
return loadingInProgress() ? loadingScreen : null;
}
LoadingScreen.propTypes = {
loading: PropTypes.bool,
darkMode: PropTypes.bool,
onFadeOutComplete: PropTypes.func,
};
export default LoadingScreen;

View File

@@ -0,0 +1,453 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import os from 'os';
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import {Grid, Row} from 'react-bootstrap';
import DotsVerticalIcon from 'mdi-react/DotsVerticalIcon';
import {ipcRenderer} from 'electron';
import {
FOCUS_BROWSERVIEW,
MAXIMIZE_CHANGE,
DARK_MODE_CHANGE,
HISTORY,
LOAD_RETRY,
LOAD_SUCCESS,
LOAD_FAILED,
SHOW_NEW_SERVER_MODAL,
SWITCH_SERVER,
WINDOW_CLOSE,
WINDOW_MINIMIZE,
WINDOW_RESTORE,
WINDOW_MAXIMIZE,
DOUBLE_CLICK_ON_WINDOW,
PLAY_SOUND,
MODAL_OPEN,
MODAL_CLOSE,
SET_SERVER_KEY,
UPDATE_MENTIONS,
TOGGLE_BACK_BUTTON,
SELECT_NEXT_TAB,
SELECT_PREVIOUS_TAB,
ADD_SERVER,
FOCUS_THREE_DOT_MENU,
} from 'common/communication';
import restoreButton from '../../assets/titlebar/chrome-restore.svg';
import maximizeButton from '../../assets/titlebar/chrome-maximize.svg';
import minimizeButton from '../../assets/titlebar/chrome-minimize.svg';
import closeButton from '../../assets/titlebar/chrome-close.svg';
import {playSound} from '../notificationSounds';
import TabBar from './TabBar.jsx';
import ExtraBar from './ExtraBar.jsx';
import ErrorView from './ErrorView.jsx';
const LOADING = 1;
const DONE = 2;
const RETRY = -1;
const FAILED = 0;
const NOSERVERS = -2;
export default class MainPage extends React.PureComponent {
constructor(props) {
super(props);
this.topBar = React.createRef();
this.threeDotMenu = React.createRef();
this.state = {
key: this.props.teams.findIndex((team) => team.order === 0),
sessionsExpired: {},
unreadCounts: {},
mentionCounts: {},
targetURL: '',
maximized: false,
tabStatus: new Map(this.props.teams.map((server) => [server.name, {status: LOADING, extra: null}])),
darkMode: this.props.darkMode,
};
}
getTabStatus() {
if (this.props.teams.length) {
const tab = this.props.teams[this.state.key];
if (tab) {
const tabname = tab.name;
return this.state.tabStatus.get(tabname);
}
}
return {status: NOSERVERS};
}
componentDidMount() {
// set page on retry
ipcRenderer.on(LOAD_RETRY, (_, server, retry, err, loadUrl) => {
console.log(`${server}: failed to load ${err}, but retrying`);
const status = this.state.tabStatus;
const statusValue = {
status: RETRY,
extra: {
retry,
error: err,
url: loadUrl,
},
};
status.set(server, statusValue);
this.setState({tabStatus: status});
});
ipcRenderer.on(LOAD_SUCCESS, (_, server) => {
const status = this.state.tabStatus;
status.set(server, {status: DONE});
this.setState({tabStatus: status});
});
ipcRenderer.on(LOAD_FAILED, (_, server, err, loadUrl) => {
console.log(`${server}: failed to load ${err}`);
const status = this.state.tabStatus;
const statusValue = {
status: FAILED,
extra: {
error: err,
url: loadUrl,
},
};
status.set(server, statusValue);
this.setState({tabStatus: status});
});
ipcRenderer.on(DARK_MODE_CHANGE, (_, darkMode) => {
this.setState({darkMode});
});
// can't switch tabs sequentially for some reason...
ipcRenderer.on(SET_SERVER_KEY, (event, key) => {
const nextIndex = this.props.teams.findIndex((team) => team.order === key);
this.handleSetServerKey(nextIndex);
});
ipcRenderer.on(SELECT_NEXT_TAB, () => {
const currentOrder = this.props.teams[this.state.key].order;
const nextOrder = ((currentOrder + 1) % this.props.teams.length);
const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder);
const team = this.props.teams[nextIndex];
this.handleSelect(team.name, nextIndex);
});
ipcRenderer.on(SELECT_PREVIOUS_TAB, () => {
const currentOrder = this.props.teams[this.state.key].order;
// js modulo operator returns a negative number if result is negative, so we have to ensure it's positive
const nextOrder = ((this.props.teams.length + (currentOrder - 1)) % this.props.teams.length);
const nextIndex = this.props.teams.findIndex((team) => team.order === nextOrder);
const team = this.props.teams[nextIndex];
this.handleSelect(team.name, nextIndex);
});
ipcRenderer.on(MAXIMIZE_CHANGE, this.handleMaximizeState);
ipcRenderer.on('enter-full-screen', () => this.handleFullScreenState(true));
ipcRenderer.on('leave-full-screen', () => this.handleFullScreenState(false));
ipcRenderer.on(ADD_SERVER, () => {
this.addServer();
});
ipcRenderer.on(PLAY_SOUND, (_event, soundName) => {
playSound(soundName);
});
ipcRenderer.on(MODAL_OPEN, () => {
this.setState({modalOpen: true});
});
ipcRenderer.on(MODAL_CLOSE, () => {
this.setState({modalOpen: false});
});
ipcRenderer.on(TOGGLE_BACK_BUTTON, (event, showExtraBar) => {
this.setState({showExtraBar});
});
ipcRenderer.on(UPDATE_MENTIONS, (_event, team, mentions, unreads, isExpired) => {
const key = this.props.teams.findIndex((server) => server.name === team);
const {unreadCounts, mentionCounts, sessionsExpired} = this.state;
const newMentionCounts = {...mentionCounts};
newMentionCounts[key] = mentions || 0;
const newUnreads = {...unreadCounts};
newUnreads[key] = unreads || false;
const expired = {...sessionsExpired};
expired[key] = isExpired || false;
this.setState({unreadCounts: newUnreads, mentionCounts: newMentionCounts, sessionsExpired: expired});
});
if (process.platform !== 'darwin') {
ipcRenderer.on(FOCUS_THREE_DOT_MENU, () => {
if (this.threeDotMenu.current) {
this.threeDotMenu.current.focus();
}
});
}
}
handleMaximizeState = (_, maximized) => {
this.setState({maximized});
}
handleFullScreenState = (isFullScreen) => {
this.setState({fullScreen: isFullScreen});
}
handleSetServerKey = (key) => {
const newKey = (this.props.teams.length + key) % this.props.teams.length;
this.setState({key: newKey});
}
handleSelect = (name, key) => {
ipcRenderer.send(SWITCH_SERVER, name);
this.handleSetServerKey(key);
}
handleDragAndDrop = async (dropResult) => {
const {removedIndex, addedIndex} = dropResult;
if (removedIndex !== addedIndex) {
const teamIndex = await this.props.moveTabs(removedIndex, addedIndex < this.props.teams.length ? addedIndex : this.props.teams.length - 1);
const name = this.props.teams[teamIndex].name;
this.handleSelect(name, teamIndex);
}
}
handleClose = (e) => {
e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back.
ipcRenderer.send(WINDOW_CLOSE);
}
handleMinimize = (e) => {
e.stopPropagation();
ipcRenderer.send(WINDOW_MINIMIZE);
}
handleMaximize = (e) => {
e.stopPropagation();
ipcRenderer.send(WINDOW_MAXIMIZE);
}
handleRestore = () => {
ipcRenderer.send(WINDOW_RESTORE);
}
openMenu = () => {
if (process.platform !== 'darwin') {
this.threeDotMenu.current.blur();
}
this.props.openMenu();
}
handleDoubleClick = () => {
ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW);
}
addServer = () => {
ipcRenderer.send(SHOW_NEW_SERVER_MODAL);
}
focusOnWebView = () => {
ipcRenderer.send(FOCUS_BROWSERVIEW);
}
setInputRef = (ref) => {
this.inputRef = ref;
}
render() {
const tabsRow = (
<TabBar
id='tabBar'
isDarkMode={this.state.darkMode}
teams={this.props.teams}
sessionsExpired={this.state.sessionsExpired}
unreadCounts={this.state.unreadCounts}
mentionCounts={this.state.mentionCounts}
activeKey={this.state.key}
onSelect={this.handleSelect}
onAddServer={this.addServer}
showAddServerButton={this.props.showAddServerButton}
onDrop={this.handleDragAndDrop}
tabsDisabled={this.state.modalOpen}
/>
);
let topBarClassName = 'topBar';
if (process.platform === 'darwin') {
topBarClassName += ' macOS';
}
if (this.state.darkMode) {
topBarClassName += ' darkMode';
}
if (this.state.fullScreen) {
topBarClassName += ' fullScreen';
}
let maxButton;
if (this.state.maximized) {
maxButton = (
<div
className='button restore-button'
onClick={this.handleRestore}
>
<img src={restoreButton}/>
</div>
);
} else {
maxButton = (
<div
className='button max-button'
onClick={this.handleMaximize}
>
<img src={maximizeButton}/>
</div>
);
}
let overlayGradient;
if (process.platform !== 'darwin') {
overlayGradient = (
<span className='overlay-gradient'/>
);
}
let titleBarButtons;
if (os.platform() === 'win32' && os.release().startsWith('10')) {
titleBarButtons = (
<span className='title-bar-btns'>
<div
className='button min-button'
onClick={this.handleMinimize}
>
<img src={minimizeButton}/>
</div>
{maxButton}
<div
className='button close-button'
onClick={this.handleClose}
>
<img src={closeButton}/>
</div>
</span>
);
}
const topRow = (
<Row
className={topBarClassName}
onDoubleClick={this.handleDoubleClick}
>
<div
ref={this.topBar}
className={`topBar-bg${this.state.unfocused ? ' unfocused' : ''}`}
>
<button
className='three-dot-menu'
onClick={this.openMenu}
tabIndex={0}
ref={this.threeDotMenu}
aria-label='Context menu'
>
<DotsVerticalIcon/>
</button>
{tabsRow}
{overlayGradient}
{titleBarButtons}
</div>
</Row>
);
const views = () => {
let component;
const tabStatus = this.getTabStatus();
if (!tabStatus) {
const tab = this.props.teams[this.state.key];
if (tab) {
console.error(`Not tabStatus for ${this.props.teams[this.state.key].name}`);
} else {
console.error('No tab status, tab doesn\'t exist anymore');
}
return null;
}
switch (tabStatus.status) {
case NOSERVERS: // TODO: substitute with https://mattermost.atlassian.net/browse/MM-25003
component = (
<ErrorView
id={'NoServers'}
className='errorView'
errorInfo={'No Servers configured'}
url={tabStatus.extra ? tabStatus.extra.url : ''}
active={true}
retry={null}
appName={this.props.appName}
/>);
break;
case FAILED:
component = (
<ErrorView
id={this.state.key + '-fail'}
className='errorView'
errorInfo={tabStatus.extra ? tabStatus.extra.error : null}
url={tabStatus.extra ? tabStatus.extra.url : ''}
active={true}
appName={this.props.appName}
/>);
break;
case LOADING:
case RETRY:
case DONE:
component = null;
}
return component;
};
const viewsRow = (
<Fragment>
<ExtraBar
darkMode={this.state.darkMode}
show={this.state.showExtraBar}
goBack={() => {
ipcRenderer.send(HISTORY, -1);
}}
/>
<Row>
{views()}
</Row>
</Fragment>);
return (
<div
className='MainPage'
onClick={this.focusOnWebView}
>
<Grid fluid={true}>
{topRow}
{viewsRow}
</Grid>
</div>
);
}
}
MainPage.propTypes = {
teams: PropTypes.array.isRequired,
showAddServerButton: PropTypes.bool.isRequired,
moveTabs: PropTypes.func.isRequired,
openMenu: PropTypes.func.isRequired,
darkMode: PropTypes.bool.isRequired,
appName: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,244 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import PropTypes from 'prop-types';
import {Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock} from 'react-bootstrap';
import urlUtils from 'common/utils/url';
export default class NewTeamModal extends React.PureComponent {
static defaultProps = {
restoreFocus: true,
};
constructor(props) {
super(props);
this.wasShown = false;
this.state = {
teamName: '',
teamUrl: '',
teamOrder: props.currentOrder || 0,
saveStarted: false,
};
}
initializeOnShow() {
this.setState({
teamName: this.props.team ? this.props.team.name : '',
teamUrl: this.props.team ? this.props.team.url : '',
teamIndex: this.props.team ? this.props.team.index : false,
teamOrder: this.props.team ? this.props.team.order : (this.props.currentOrder || 0),
saveStarted: false,
});
}
getTeamNameValidationError() {
if (!this.state.saveStarted) {
return null;
}
return this.state.teamName.length > 0 ? null : 'Name is required.';
}
getTeamNameValidationState() {
return this.getTeamNameValidationError() === null ? null : 'error';
}
handleTeamNameChange = (e) => {
this.setState({
teamName: e.target.value,
});
}
getTeamUrlValidationError() {
if (!this.state.saveStarted) {
return null;
}
if (this.state.teamUrl.length === 0) {
return 'URL is required.';
}
if (!(/^https?:\/\/.*/).test(this.state.teamUrl.trim())) {
return 'URL should start with http:// or https://.';
}
if (!urlUtils.isValidURL(this.state.teamUrl.trim())) {
return 'URL is not formatted correctly.';
}
return null;
}
getTeamUrlValidationState() {
return this.getTeamUrlValidationError() === null ? null : 'error';
}
handleTeamUrlChange = (e) => {
this.setState({
teamUrl: e.target.value,
});
}
getError() {
const nameError = this.getTeamNameValidationError();
const urlError = this.getTeamUrlValidationError();
if (nameError && urlError) {
return 'Name and URL are required.';
} else if (nameError) {
return nameError;
} else if (urlError) {
return urlError;
}
return null;
}
validateForm() {
return this.getTeamNameValidationState() === null &&
this.getTeamUrlValidationState() === null;
}
save = () => {
this.setState({
saveStarted: true,
}, () => {
if (this.validateForm()) {
this.props.onSave({
url: this.state.teamUrl,
name: this.state.teamName,
index: this.state.teamIndex,
order: this.state.teamOrder,
});
}
});
}
getSaveButtonLabel() {
if (this.props.editMode) {
return 'Save';
}
return 'Add';
}
getModalTitle() {
if (this.props.editMode) {
return 'Edit Server';
}
return 'Add Server';
}
render() {
if (this.wasShown !== this.props.show && this.props.show) {
this.initializeOnShow();
}
this.wasShown = this.props.show;
return (
<Modal
bsClass='modal'
className='NewTeamModal'
show={this.props.show}
id='newServerModal'
enforceFocus={true}
onEntered={() => this.teamNameInputRef.focus()}
onHide={this.props.onClose}
restoreFocus={this.props.restoreFocus}
onKeyDown={(e) => {
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;
}
}}
>
<Modal.Header>
<Modal.Title>{this.getModalTitle()}</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<FormGroup
validationState={this.getTeamNameValidationState()}
>
<ControlLabel>{'Server Display Name'}</ControlLabel>
<FormControl
id='teamNameInput'
type='text'
value={this.state.teamName}
placeholder='Server Name'
onChange={this.handleTeamNameChange}
inputRef={(ref) => {
this.teamNameInputRef = ref;
if (this.props.setInputRef) {
this.props.setInputRef(ref);
}
}}
onClick={(e) => {
e.stopPropagation();
}}
autoFocus={true}
/>
<FormControl.Feedback/>
<HelpBlock>{'The name of the server displayed on your desktop app tab bar.'}</HelpBlock>
</FormGroup>
<FormGroup
className='NewTeamModal-noBottomSpace'
validationState={this.getTeamUrlValidationState()}
>
<ControlLabel>{'Server URL'}</ControlLabel>
<FormControl
id='teamUrlInput'
type='text'
value={this.state.teamUrl}
placeholder='https://example.com'
onChange={this.handleTeamUrlChange}
onClick={(e) => {
e.stopPropagation();
}}
/>
<FormControl.Feedback/>
<HelpBlock className='NewTeamModal-noBottomSpace'>{'The URL of your Mattermost server. Must start with http:// or https://.'}</HelpBlock>
</FormGroup>
</form>
</Modal.Body>
<Modal.Footer>
<div
className='pull-left modal-error'
>
{this.getError()}
</div>
<Button
id='cancelNewServerModal'
onClick={this.props.onClose}
>{'Cancel'}</Button>
<Button
id='saveNewServerModal'
onClick={this.save}
disabled={!this.validateForm()}
bsStyle='primary'
>{this.getSaveButtonLabel()}</Button>
</Modal.Footer>
</Modal>
);
}
}
NewTeamModal.propTypes = {
onClose: PropTypes.func,
onSave: PropTypes.func,
team: PropTypes.object,
editMode: PropTypes.bool,
show: PropTypes.bool,
restoreFocus: PropTypes.bool,
currentOrder: PropTypes.number,
setInputRef: PropTypes.func,
};

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import PropTypes from 'prop-types';
import {Modal} from 'react-bootstrap';
import DestructiveConfirmationModal from './DestructiveConfirmModal.jsx';
export default function RemoveServerModal(props) {
const {serverName, ...rest} = props;
return (
<DestructiveConfirmationModal
{...rest}
title='Remove Server'
acceptLabel='Remove'
cancelLabel='Cancel'
body={(
<Modal.Body>
<p>
{'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>
{'Confirm you wish to remove the '}<strong>{serverName}</strong>{' server?'}
</p>
</Modal.Body>
)}
/>
);
}
RemoveServerModal.propTypes = {
serverName: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,723 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import 'renderer/css/settings.css';
import React from 'react';
import PropTypes from 'prop-types';
import {Checkbox, Col, FormGroup, Grid, HelpBlock, Navbar, Radio, Row, Button} from 'react-bootstrap';
import {ipcRenderer} from 'electron';
import {debounce} from 'underscore';
import {GET_LOCAL_CONFIGURATION, UPDATE_CONFIGURATION, DOUBLE_CLICK_ON_WINDOW, GET_DOWNLOAD_LOCATION, SWITCH_SERVER, ADD_SERVER, RELOAD_CONFIGURATION} from 'common/communication';
import TeamList from './TeamList.jsx';
import AutoSaveIndicator from './AutoSaveIndicator.jsx';
const CONFIG_TYPE_SERVERS = 'servers';
const CONFIG_TYPE_APP_OPTIONS = 'appOptions';
function backToIndex(serverName) {
ipcRenderer.send(SWITCH_SERVER, serverName);
window.close();
}
export default class SettingsPage extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
ready: false,
teams: [],
showAddTeamForm: false,
savingState: {
appOptions: AutoSaveIndicator.SAVING_STATE_DONE,
servers: AutoSaveIndicator.SAVING_STATE_DONE,
},
userOpenedDownloadDialog: false,
};
this.getConfig();
this.trayIconThemeRef = React.createRef();
this.downloadLocationRef = React.createRef();
this.showTrayIconRef = React.createRef();
this.autostartRef = React.createRef();
this.minimizeToTrayRef = React.createRef();
this.flashWindowRef = React.createRef();
this.bounceIconRef = React.createRef();
this.showUnreadBadgeRef = React.createRef();
this.useSpellCheckerRef = React.createRef();
this.enableHardwareAccelerationRef = React.createRef();
this.saveQueue = [];
}
componentDidMount() {
ipcRenderer.on(ADD_SERVER, () => {
this.setState({
showAddTeamForm: true,
});
});
ipcRenderer.on(RELOAD_CONFIGURATION, () => {
this.updateSaveState();
this.getConfig();
});
}
getConfig = () => {
ipcRenderer.invoke(GET_LOCAL_CONFIGURATION).then((config) => {
this.setState({ready: true, maximized: false, ...this.convertConfigDataToState(config)});
});
}
convertConfigDataToState = (configData, currentState = {}) => {
const newState = Object.assign({}, configData);
newState.showAddTeamForm = currentState.showAddTeamForm || false;
newState.trayWasVisible = currentState.trayWasVisible || false;
if (newState.teams.length === 0 && currentState.firstRun !== false) {
newState.firstRun = false;
newState.showAddTeamForm = true;
}
newState.savingState = currentState.savingState || {
appOptions: AutoSaveIndicator.SAVING_STATE_DONE,
servers: AutoSaveIndicator.SAVING_STATE_DONE,
};
return newState;
}
saveSetting = (configType, {key, data}) => {
this.saveQueue.push({
configType,
key,
data,
});
this.updateSaveState();
this.processSaveQueue();
}
processSaveQueue = debounce(() => {
ipcRenderer.send(UPDATE_CONFIGURATION, this.saveQueue.splice(0, this.saveQueue.length));
}, 500);
updateSaveState = () => {
let queuedUpdateCounts = {
[CONFIG_TYPE_SERVERS]: 0,
[CONFIG_TYPE_APP_OPTIONS]: 0,
};
queuedUpdateCounts = this.saveQueue.reduce((updateCounts, {configType}) => {
updateCounts[configType]++;
return updateCounts;
}, queuedUpdateCounts);
const savingState = Object.assign({}, this.state.savingState);
Object.entries(queuedUpdateCounts).forEach(([configType, count]) => {
if (count > 0) {
savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVING;
} else if (count === 0 && savingState[configType] === AutoSaveIndicator.SAVING_STATE_SAVING) {
savingState[configType] = AutoSaveIndicator.SAVING_STATE_SAVED;
this.resetSaveState(configType);
}
});
this.setState({savingState});
}
resetSaveState = debounce((configType) => {
if (this.state.savingState[configType] !== AutoSaveIndicator.SAVING_STATE_SAVING) {
const savingState = Object.assign({}, this.state.savingState);
savingState[configType] = AutoSaveIndicator.SAVING_STATE_DONE;
this.setState({savingState});
}
}, 2000);
handleTeamsChange = (teams) => {
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({
showAddTeamForm: false,
teams,
});
if (teams.length === 0) {
this.setState({showAddTeamForm: true});
}
}
handleChangeShowTrayIcon = () => {
const shouldShowTrayIcon = !this.showTrayIconRef.current.props.checked;
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showTrayIcon', data: shouldShowTrayIcon});
this.setState({
showTrayIcon: shouldShowTrayIcon,
});
if (process.platform === 'darwin' && !shouldShowTrayIcon) {
this.setState({
minimizeToTray: false,
});
}
}
handleChangeTrayIconTheme = (theme) => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'trayIconTheme', data: theme});
this.setState({
trayIconTheme: theme,
});
}
handleChangeAutoStart = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'autostart', data: !this.autostartRef.current.props.checked});
this.setState({
autostart: !this.autostartRef.current.props.checked,
});
}
handleChangeMinimizeToTray = () => {
const shouldMinimizeToTray = this.state.showTrayIcon && !this.minimizeToTrayRef.current.props.checked;
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'minimizeToTray', data: shouldMinimizeToTray});
this.setState({
minimizeToTray: shouldMinimizeToTray,
});
}
toggleShowTeamForm = () => {
this.setState({
showAddTeamForm: !this.state.showAddTeamForm,
});
document.activeElement.blur();
}
setShowTeamFormVisibility = (val) => {
this.setState({
showAddTeamForm: val,
});
}
handleFlashWindow = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications',
data: {
...this.state.notifications,
flashWindow: this.flashWindowRef.current.props.checked ? 0 : 2,
},
});
this.setState({
notifications: {
...this.state.notifications,
flashWindow: this.flashWindowRef.current.props.checked ? 0 : 2,
},
});
}
handleBounceIcon = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications',
data: {
...this.state.notifications,
bounceIcon: !this.bounceIconRef.current.props.checked,
},
});
this.setState({
notifications: {
...this.state.notifications,
bounceIcon: !this.bounceIconRef.current.props.checked,
},
});
}
handleBounceIconType = (event) => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {
key: 'notifications',
data: {
...this.state.notifications,
bounceIconType: event.target.value,
},
});
this.setState({
notifications: {
...this.state.notifications,
bounceIconType: event.target.value,
},
});
}
handleShowUnreadBadge = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'showUnreadBadge', data: !this.showUnreadBadgeRef.current.props.checked});
this.setState({
showUnreadBadge: !this.showUnreadBadgeRef.current.props.checked,
});
}
handleChangeUseSpellChecker = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'useSpellChecker', data: !this.useSpellCheckerRef.current.props.checked});
this.setState({
useSpellChecker: !this.useSpellCheckerRef.current.props.checked,
});
}
handleChangeEnableHardwareAcceleration = () => {
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'enableHardwareAcceleration', data: !this.enableHardwareAccelerationRef.current.props.checked});
this.setState({
enableHardwareAcceleration: !this.enableHardwareAccelerationRef.current.props.checked,
});
}
saveDownloadLocation = (location) => {
this.setState({
downloadLocation: location,
});
setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'downloadLocation', data: location});
}
handleChangeDownloadLocation = (e) => {
this.saveDownloadLocation(e.target.value);
}
selectDownloadLocation = () => {
if (!this.state.userOpenedDownloadDialog) {
ipcRenderer.invoke(GET_DOWNLOAD_LOCATION, `/Users/${process.env.USER || process.env.USERNAME}/Downloads`).then((result) => this.saveDownloadLocation(result));
this.setState({userOpenedDownloadDialog: true});
}
this.setState({userOpenedDownloadDialog: false});
}
updateTeam = (index, newData) => {
const teams = this.state.teams;
teams[index] = newData;
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({
teams,
});
}
addServer = (team) => {
const teams = this.state.teams;
teams.push(team);
setImmediate(this.saveSetting, CONFIG_TYPE_SERVERS, {key: 'teams', data: teams});
this.setState({
teams,
});
}
openMenu = () => {
// @eslint-ignore
this.threeDotMenu.current.blur();
this.props.openMenu();
}
handleDoubleClick = () => {
ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, 'settings');
}
render() {
const settingsPage = {
navbar: {
backgroundColor: '#fff',
position: 'relative',
},
close: {
textDecoration: 'none',
position: 'absolute',
right: '0',
top: '5px',
fontSize: '35px',
fontWeight: 'normal',
color: '#bbb',
},
heading: {
textAlign: 'center',
fontSize: '24px',
margin: '0',
padding: '1em 0',
},
sectionHeading: {
fontSize: '20px',
margin: '0',
padding: '1em 0',
float: 'left',
},
sectionHeadingLink: {
marginTop: '24px',
display: 'inline-block',
fontSize: '15px',
},
footer: {
padding: '0.4em 0',
},
downloadLocationInput: {
marginRight: '3px',
marginTop: '8px',
width: '320px',
height: '34px',
padding: '0 12px',
borderRadius: '4px',
border: '1px solid #ccc',
fontWeight: '500',
},
downloadLocationButton: {
marginBottom: '4px',
},
container: {
paddingBottom: '40px',
},
};
const teamsRow = (
<Row>
<Col md={12}>
<TeamList
teams={this.state.teams}
showAddTeamForm={this.state.showAddTeamForm}
toggleAddTeamForm={this.toggleShowTeamForm}
setAddTeamFormVisibility={this.setShowTeamFormVisibility}
onTeamsChange={this.handleTeamsChange}
updateTeam={this.updateTeam}
addServer={this.addServer}
allowTeamEdit={this.state.enableServerManagement}
onTeamClick={(name) => {
backToIndex(name);
}}
/>
</Col>
</Row>
);
const serversRow = (
<Row>
<Col
md={10}
xs={8}
>
<h2 style={settingsPage.sectionHeading}>{'Server Management'}</h2>
<div className='IndicatorContainer'>
<AutoSaveIndicator
id='serversSaveIndicator'
savingState={this.state.savingState.servers}
errorMessage={'Can\'t save your changes. Please try again.'}
/>
</div>
</Col>
<Col
md={2}
xs={4}
>
<p className='text-right'>
<a
style={settingsPage.sectionHeadingLink}
id='addNewServer'
href='#'
onClick={this.toggleShowTeamForm}
>{'+ Add New Server'}</a>
</p>
</Col>
</Row>
);
let srvMgmt;
if (this.state.enableServerManagement === true) {
srvMgmt = (
<div>
{serversRow}
{teamsRow}
<hr/>
</div>
);
}
const options = [];
// MacOS has an option in the Dock, to set the app to autostart, so we choose to not support this option for OSX
if (process.platform === 'win32' || process.platform === 'linux') {
options.push(
<Checkbox
key='inputAutoStart'
id='inputAutoStart'
ref={this.autostartRef}
checked={this.state.autostart}
onChange={this.handleChangeAutoStart}
>
{'Start app on login'}
<HelpBlock>
{'If enabled, the app starts automatically when you log in to your machine.'}
</HelpBlock>
</Checkbox>);
}
options.push(
<Checkbox
key='inputSpellChecker'
id='inputSpellChecker'
ref={this.useSpellCheckerRef}
checked={this.state.useSpellChecker}
onChange={this.handleChangeUseSpellChecker}
>
{'Check spelling'}
<HelpBlock>
{'Highlight misspelled words in your messages.'}
{' Available for English, French, German, Portuguese, Spanish, and Dutch.'}
</HelpBlock>
</Checkbox>);
if (process.platform === 'darwin' || process.platform === 'win32') {
const TASKBAR = process.platform === 'win32' ? 'taskbar' : 'Dock';
options.push(
<Checkbox
key='inputShowUnreadBadge'
id='inputShowUnreadBadge'
ref={this.showUnreadBadgeRef}
checked={this.state.showUnreadBadge}
onChange={this.handleShowUnreadBadge}
>
{`Show red badge on ${TASKBAR} icon to indicate unread messages`}
<HelpBlock>
{`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`}
</HelpBlock>
</Checkbox>);
}
if (process.platform === 'win32' || process.platform === 'linux') {
options.push(
<Checkbox
key='flashWindow'
id='inputflashWindow'
ref={this.flashWindowRef}
checked={!this.state.notifications || this.state.notifications.flashWindow === 2}
onChange={this.handleFlashWindow}
>
{'Flash app window and taskbar icon when a new message is received'}
<HelpBlock>
{'If enabled, app window and taskbar icon flash for a few seconds when a new message is received.'}
</HelpBlock>
</Checkbox>);
}
if (process.platform === 'darwin') {
options.push(
<FormGroup
key='OptionsForm'
>
<Checkbox
inline={true}
key='bounceIcon'
id='inputBounceIcon'
ref={this.bounceIconRef}
checked={this.state.notifications ? this.state.notifications.bounceIcon : false}
onChange={this.handleBounceIcon}
style={{marginRight: '10px'}}
>
{'Bounce the Dock icon'}
</Checkbox>
<Radio
inline={true}
name='bounceIconType'
value='informational'
disabled={!this.state.notifications || !this.state.notifications.bounceIcon}
defaultChecked={
!this.state.notifications ||
!this.state.notifications.bounceIconType ||
this.state.notifications.bounceIconType === 'informational'
}
onChange={this.handleBounceIconType}
>
{'once'}
</Radio>
{' '}
<Radio
inline={true}
name='bounceIconType'
value='critical'
disabled={!this.state.notifications || !this.state.notifications.bounceIcon}
defaultChecked={this.state.notifications && this.state.notifications.bounceIconType === 'critical'}
onChange={this.handleBounceIconType}
>
{'until I open the app'}
</Radio>
<HelpBlock
style={{marginLeft: '20px'}}
>
{'If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.'}
</HelpBlock>
</FormGroup>,
);
}
if (process.platform === 'darwin' || process.platform === 'linux') {
options.push(
<Checkbox
key='inputShowTrayIcon'
id='inputShowTrayIcon'
ref={this.showTrayIconRef}
checked={this.state.showTrayIcon}
onChange={this.handleChangeShowTrayIcon}
>
{process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'}
<HelpBlock>
{'Setting takes effect after restarting the app.'}
</HelpBlock>
</Checkbox>);
}
if (process.platform === 'linux') {
options.push(
<FormGroup
key='trayIconTheme'
ref={this.trayIconThemeRef}
style={{marginLeft: '20px'}}
>
{'Icon theme: '}
<Radio
inline={true}
name='trayIconTheme'
value='light'
defaultChecked={this.state.trayIconTheme === 'light' || !this.state.trayIconTheme}
onChange={(event) => this.handleChangeTrayIconTheme('light', event)}
>
{'Light'}
</Radio>
{' '}
<Radio
inline={true}
name='trayIconTheme'
value='dark'
defaultChecked={this.state.trayIconTheme === 'dark'}
onChange={(event) => this.handleChangeTrayIconTheme('dark', event)}
>{'Dark'}</Radio>
</FormGroup>,
);
}
if (process.platform === 'linux') {
options.push(
<Checkbox
key='inputMinimizeToTray'
id='inputMinimizeToTray'
ref={this.minimizeToTrayRef}
disabled={!this.state.showTrayIcon || !this.state.trayWasVisible}
checked={this.state.minimizeToTray}
onChange={this.handleChangeMinimizeToTray}
>
{'Leave app running in notification area when application window is closed'}
<HelpBlock>
{'If enabled, the app stays running in the notification area after app window is closed.'}
{this.state.trayWasVisible || !this.state.showTrayIcon ? '' : ' Setting takes effect after restarting the app.'}
</HelpBlock>
</Checkbox>);
}
options.push(
<Checkbox
key='inputEnableHardwareAcceleration'
id='inputEnableHardwareAcceleration'
ref={this.enableHardwareAccelerationRef}
checked={this.state.enableHardwareAcceleration}
onChange={this.handleChangeEnableHardwareAcceleration}
>
{'Use GPU hardware acceleration'}
<HelpBlock>
{'If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.'}
{' Setting takes effect after restarting the app.'}
</HelpBlock>
</Checkbox>,
);
options.push(
<div style={settingsPage.container}>
<hr/>
<div>{'Download Location'}</div>
<input
disabled={true}
style={settingsPage.downloadLocationInput}
key='inputDownloadLocation'
id='inputDownloadLocation'
ref={this.downloadLocationRef}
onChange={this.handleChangeDownloadLocation}
value={this.state.downloadLocation}
/>
<Button
style={settingsPage.downloadLocationButton}
id='saveDownloadLocation'
onClick={this.selectDownloadLocation}
>
<span>{'Change'}</span>
</Button>
<HelpBlock>
{'Specify the folder where files will download.'}
</HelpBlock>
</div>,
);
let optionsRow = null;
if (options.length > 0) {
optionsRow = (
<Row>
<Col md={12}>
<h2 style={settingsPage.sectionHeading}>{'App Options'}</h2>
<div className='IndicatorContainer'>
<AutoSaveIndicator
id='appOptionsSaveIndicator'
savingState={this.state.savingState.appOptions}
errorMessage={'Can\'t save your changes. Please try again.'}
/>
</div>
{ options.map((opt) => (
<FormGroup key={opt.key}>
{opt}
</FormGroup>
)) }
</Col>
</Row>
);
}
let waitForIpc;
if (this.state.ready) {
waitForIpc = (
<>
{srvMgmt}
{optionsRow}
</>
);
} else {
waitForIpc = (<p>{'Loading configuration...'}</p>);
}
return (
<div
className='container-fluid'
style={{
height: '100%',
}}
>
<div
style={{
overflowY: 'auto',
height: '100%',
margin: '0 -15px',
}}
>
<Navbar
className='navbar-fixed-top'
style={settingsPage.navbar}
>
<div style={{position: 'relative'}}>
<h1 style={settingsPage.heading}>{'Settings'}</h1>
</div>
</Navbar>
<Grid
className='settingsPage'
>
{waitForIpc}
</Grid>
</div>
</div>
);
}
}
SettingsPage.propTypes = {
openMenu: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,154 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import {ipcRenderer} from 'electron';
import PropTypes from 'prop-types';
import {Nav, NavItem} from 'react-bootstrap';
import {Container, Draggable} from 'react-smooth-dnd';
import PlusIcon from 'mdi-react/PlusIcon';
import {GET_CONFIGURATION} from 'common/communication';
export default class TabBar extends React.PureComponent { // need "this"
constructor(props) {
super(props);
this.state = {
hasGPOTeams: false,
};
}
componentDidMount() {
ipcRenderer.invoke(GET_CONFIGURATION).then((config) => {
this.setState({hasGPOTeams: config.registryTeams && config.registryTeams.length > 0});
});
}
render() {
const orderedTabs = this.props.teams.concat().sort((a, b) => a.order - b.order);
const tabs = orderedTabs.map((team) => {
const index = this.props.teams.indexOf(team);
const sessionExpired = this.props.sessionsExpired[index];
const hasUnreads = this.props.unreadCounts[index];
let mentionCount = 0;
if (this.props.mentionCounts[index] > 0) {
mentionCount = this.props.mentionCounts[index];
}
let badgeDiv;
if (sessionExpired) {
badgeDiv = (
<div className='TabBar-expired'/>
);
} else if (mentionCount !== 0) {
badgeDiv = (
<div className='TabBar-badge'>
{mentionCount}
</div>
);
} else if (hasUnreads) {
badgeDiv = (
<div className='TabBar-dot'/>
);
}
const id = `teamTabItem${index}`;
const navItem = () => (
<NavItem
key={index}
id={id}
eventKey={index}
draggable={false}
ref={id}
active={this.props.activeKey === index}
activeKey={this.props.activeKey}
onMouseDown={() => {
this.props.onSelect(team.name, index);
}}
onSelect={() => {
this.props.onSelect(team.name, index);
}}
title={team.name}
disabled={this.props.tabsDisabled}
>
<div className='TabBar-tabSeperator'>
<span>
{team.name}
</span>
{ badgeDiv }
</div>
</NavItem>
);
return (
<Draggable
key={id}
render={navItem}
className='teamTabItem'
/>);
});
if (this.props.showAddServerButton === true) {
tabs.push(
<NavItem
className='TabBar-addServerButton'
key='addServerButton'
id='addServerButton'
eventKey='addServerButton'
draggable={false}
title='Add new server'
activeKey={this.props.activeKey}
onSelect={() => {
this.props.onAddServer();
}}
disabled={this.props.tabsDisabled}
>
<div className='TabBar-tabSeperator'>
<PlusIcon size={20}/>
</div>
</NavItem>,
);
}
const navContainer = (ref) => (
<Nav
ref={ref}
className={`smooth-dnd-container TabBar${this.props.isDarkMode ? ' darkMode' : ''}`}
id={this.props.id}
bsStyle='tabs'
>
{ tabs }
</Nav>
);
return (
<Container
ref={this.container}
render={navContainer}
orientation='horizontal'
lockAxis={'x'}
onDrop={this.props.onDrop}
animationDuration={300}
shouldAcceptDrop={() => {
return !this.state.hasGPOTeams && !this.props.tabsDisabled;
}}
/>
);
}
}
TabBar.propTypes = {
activeKey: PropTypes.number,
id: PropTypes.string,
isDarkMode: PropTypes.bool,
onSelect: PropTypes.func,
teams: PropTypes.array,
sessionsExpired: PropTypes.object,
unreadCounts: PropTypes.object,
mentionCounts: PropTypes.object,
showAddServerButton: PropTypes.bool,
onAddServer: PropTypes.func,
onDrop: PropTypes.func,
tabsDisabled: PropTypes.bool,
};

View File

@@ -0,0 +1,187 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import PropTypes from 'prop-types';
import {ListGroup} from 'react-bootstrap';
import TeamListItem from './TeamListItem.jsx';
import NewTeamModal from './NewTeamModal.jsx';
import RemoveServerModal from './RemoveServerModal.jsx';
export default class TeamList extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
showEditTeamForm: false,
indexToRemoveServer: -1,
team: {
url: '',
name: '',
index: false,
order: props.teams.length,
},
};
}
handleTeamRemove = (index) => {
console.log(index);
const teams = this.props.teams;
const removedOrder = this.props.teams[index].order;
teams.splice(index, 1);
teams.forEach((value) => {
if (value.order > removedOrder) {
value.order--;
}
});
this.props.onTeamsChange(teams);
}
handleTeamAdd = (team) => {
const teams = this.props.teams;
// check if team already exists and then change existing team or add new one
if ((typeof team.index !== 'undefined') && teams[team.index]) {
teams[team.index].name = team.name;
teams[team.index].url = team.url;
teams[team.index].order = team.order;
} else {
teams.push(team);
}
this.setState({
showEditTeamForm: false,
team: {
url: '',
name: '',
index: false,
order: teams.length,
},
});
this.props.onTeamsChange(teams);
}
openServerRemoveModal = (indexForServer) => {
this.setState({indexToRemoveServer: indexForServer});
}
closeServerRemoveModal = () => {
this.setState({indexToRemoveServer: -1});
}
handleTeamRemovePrompt = (index) => {
return () => {
document.activeElement.blur();
this.openServerRemoveModal(index);
};
}
handleTeamEditing = (team, index) => {
return () => {
document.activeElement.blur();
this.setState({
showEditTeamForm: true,
team: {
url: team.url,
name: team.name,
index,
order: team.order,
},
});
};
}
render() {
const teamNodes = this.props.teams.map((team, i) => {
return (
<TeamListItem
key={`teamListItem_${team.name}`}
name={team.name}
url={team.url}
onTeamRemove={this.handleTeamRemovePrompt(i)}
onTeamEditing={this.handleTeamEditing(team, i)}
onTeamClick={() => this.props.onTeamClick(team.name)}
/>
);
});
const addServerForm = (
<NewTeamModal
currentOrder={this.props.teams.length}
show={this.props.showAddTeamForm || this.state.showEditTeamForm}
editMode={this.state.showEditTeamForm}
onClose={() => {
this.setState({
showEditTeamForm: false,
team: {
name: '',
url: '',
index: false,
order: this.props.teams.length,
},
});
this.props.setAddTeamFormVisibility(false);
}}
onSave={(newTeam) => {
const teamData = {
name: newTeam.name,
url: newTeam.url,
order: newTeam.order,
};
if (this.props.showAddTeamForm) {
this.props.addServer(teamData);
} else {
this.props.updateTeam(newTeam.index, teamData);
}
this.setState({
showNewTeamModal: false,
showEditTeamForm: false,
team: {
name: '',
url: '',
index: false,
order: newTeam.order + 1,
},
});
this.render();
this.props.setAddTeamFormVisibility(false);
}}
team={this.state.team}
/>);
const removeServer = this.props.teams[this.state.indexToRemoveServer];
const removeServerModal = (
<RemoveServerModal
show={this.state.indexToRemoveServer !== -1}
serverName={removeServer ? removeServer.name : ''}
onHide={this.closeServerRemoveModal}
onCancel={this.closeServerRemoveModal}
onAccept={() => {
this.handleTeamRemove(this.state.indexToRemoveServer);
this.closeServerRemoveModal();
}}
/>
);
return (
<ListGroup className='teamList'>
{ teamNodes }
{ addServerForm }
{ removeServerModal}
</ListGroup>
);
}
}
TeamList.propTypes = {
onTeamClick: PropTypes.func,
onTeamsChange: PropTypes.func,
showAddTeamForm: PropTypes.bool,
teams: PropTypes.array,
addServer: PropTypes.func,
updateTeam: PropTypes.func,
setAddTeamFormVisibility: PropTypes.func,
};

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import PropTypes from 'prop-types';
export default class TeamListItem extends React.PureComponent {
handleTeamRemove = () => {
this.props.onTeamRemove();
}
handleTeamEditing = () => {
this.props.onTeamEditing();
}
render() {
return (
<div className='TeamListItem list-group-item'>
<div
className='TeamListItem-left'
onClick={this.props.onTeamClick}
>
<h4 className='list-group-item-heading'>{ this.props.name }</h4>
<p className='list-group-item-text'>
{ this.props.url }
</p>
</div>
<div className='pull-right'>
<a
href='#'
onClick={this.handleTeamEditing}
>{'Edit'}</a>
{' - '}
<a
href='#'
onClick={this.handleTeamRemove}
>{'Remove'}</a>
</div>
</div>
);
}
}
TeamListItem.propTypes = {
name: PropTypes.string,
onTeamEditing: PropTypes.func,
onTeamRemove: PropTypes.func,
onTeamClick: PropTypes.func,
url: PropTypes.string,
};

View File

@@ -0,0 +1,113 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import propTypes from 'prop-types';
import {Button, Navbar, ProgressBar} from 'react-bootstrap';
function InstallButton(props) {
if (props.notifyOnly) {
return (
<Button
bsStyle='primary'
onClick={props.onClickDownload}
>{'Download Update'}</Button>
);
}
return (
<Button
bsStyle='primary'
onClick={props.onClickInstall}
>{'Install Update'}</Button>
);
}
InstallButton.propTypes = {
notifyOnly: propTypes.bool.isRequired,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
};
function UpdaterPage(props) {
let footer;
if (props.isDownloading) {
footer = (
<Navbar
className='UpdaterPage-footer'
fixedBottom={true}
fluid={true}
>
<ProgressBar
active={true}
now={props.progress}
label={`${props.progress}%`}
/>
<div className='pull-right'>
<Button
onClick={props.onClickCancel}
>{'Cancel'}</Button>
</div>
</Navbar>
);
} else {
footer = (
<Navbar
className='UpdaterPage-footer'
fixedBottom={true}
fluid={true}
>
<Button
className='UpdaterPage-skipButton'
bsStyle='link'
onClick={props.onClickSkip}
>{'Skip this version'}</Button>
<div className='pull-right'>
<Button
bsStyle='link'
onClick={props.onClickRemind}
>{'Remind me in 2 days'}</Button>
<InstallButton
notifyOnly={props.notifyOnly}
onClickInstall={props.onClickInstall}
onClickDownload={props.onClickDownload}
/>
</div>
</Navbar>
);
}
return (
<div className='UpdaterPage'>
<Navbar fluid={true} >
<h1 className='UpdaterPage-heading'>{'New update is available'}</h1>
</Navbar>
<div className='container-fluid'>
<p>{`A new version of the ${props.appName} is available!`}</p>
<p>{'Read the '}
<a
href='#'
onClick={props.onClickReleaseNotes}
>{'release notes'}</a>
{' to learn more.'}
</p>
</div>
{footer}
</div>
);
}
UpdaterPage.propTypes = {
appName: propTypes.string.isRequired,
notifyOnly: propTypes.bool.isRequired,
isDownloading: propTypes.bool.isRequired,
progress: propTypes.number,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
onClickReleaseNotes: propTypes.func.isRequired,
onClickRemind: propTypes.func.isRequired,
onClickSkip: propTypes.func.isRequired,
onClickCancel: propTypes.func.isRequired,
};
export default UpdaterPage;

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2015-2016 Yuya Ochiai
import React from 'react';
import {storiesOf} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import UpdaterPage from '../UpdaterPage.jsx';
import '../../css/components/UpdaterPage.css';
/*
appName: propTypes.string.isRequired,
notifyOnly: propTypes.bool.isRequired,
isDownloading: propTypes.bool.isRequired,
progress: propTypes.number,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
onClickReleaseNotes: propTypes.func.isRequired,
onClickRemind: propTypes.func.isRequired,
onClickSkip: propTypes.func.isRequired,
*/
const appName = 'Storybook App';
storiesOf('UpdaterPage', module).
add('Normal', () => (
<UpdaterPage
appName={appName}
notifyOnly={false}
isDownloading={false}
progress={0}
onClickInstall={action('clicked install')}
onClickReleaseNotes={action('clicked release notes')}
onClickRemind={action('clicked remind')}
onClickSkip={action('clicked skip')}
/>
)).
add('NotifyOnly', () => (
<UpdaterPage
appName={appName}
notifyOnly={true}
onClickDownload={action('clicked download')}
/>
)).
add('Downloading', () => (
<UpdaterPage
appName={appName}
isDownloading={true}
progress={0}
onClickCancel={action('clicked cancel')}
/>
));

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import {Modal, Button, Row, Col} from 'react-bootstrap';
export default class ShowCertificateModal extends React.PureComponent {
static propTypes = {
certificate: PropTypes.object,
onOk: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
certificate: props.certificate,
};
}
handleOk = () => {
this.setState({certificate: null});
this.props.onOk();
}
render() {
const certificateSection = (descriptor) => {
return (
<Fragment>
<dt className={'certificate-key'}>{descriptor}</dt>
<dd className={'certificate-section'}><span/></dd>
</Fragment>
);
};
const certificateItem = (descriptor, value) => {
const val = value ? `${value}` : <span/>;
return (
<Fragment>
<dt className={'certificate-key'}>{descriptor}</dt>
<dd className={'certificate-value'}>{val}</dd>
</Fragment>
);
};
if (this.state.certificate === null) {
return (
<Modal
bsClass='modal'
className='show-certificate'
>
<Modal.Body>
{'No certificate Selected'}
</Modal.Body>
</Modal>
);
}
const utcSeconds = (date) => {
const d = new Date(0);
d.setUTCSeconds(date);
return d;
};
const expiration = utcSeconds(this.state.certificate.validExpiry);
const creation = utcSeconds(this.state.certificate.validStart);
const dateDisplayOptions = {dateStyle: 'full', timeStyle: 'full'};
const dateLocale = 'en-US';
return (
<Modal
bsClass='modal'
className='show-certificate'
show={this.state.certificate !== null}
scrollable={'true'}
>
<Modal.Header className={'no-border'}>
<Modal.Title>{'Certificate information'}</Modal.Title>
</Modal.Header>
<Modal.Body>
<p className='details'>{'Details'}</p>
<dl>
{certificateSection('Subject Name')}
{certificateItem('Common Name', this.state.certificate.subject.commonName)}
</dl>
<dl>
{certificateSection('Issuer Name')}
{certificateItem('Common Name', this.state.certificate.issuer.commonName)}
</dl>
<dl>
{certificateItem('Serial Number', this.state.certificate.serialNumber)}
{certificateItem('Not Valid Before', creation.toLocaleString(dateLocale, dateDisplayOptions))}
{certificateItem('Not Valid After', expiration.toLocaleString(dateLocale, dateDisplayOptions))}
</dl>
<dl>
{certificateSection('Public Key Info')}
{certificateItem('Algorithm', 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'}
>{'Close'}</Button>
</Col>
</Row>
</div>
</Modal.Footer>
</Modal>
);
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import propTypes from 'prop-types';
export default function UrlDescription(props) {
if (props.url) {
return (
<div className='HoveringURL HoveringURL-left'>
<p>{props.url}</p>
</div>
);
}
}
UrlDescription.propTypes = {
url: propTypes.string,
};