diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 30cc55377c..4376439012 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -1,11 +1,9 @@ # autogenerated file: run scripts/generate-eslint-error-ignore-file to update. src/component-index.js +src/components/structures/auth/ForgotPassword.js src/components/structures/BottomLeftMenu.js -src/components/structures/CompatibilityPage.js src/components/structures/CreateRoom.js -src/components/structures/LoggedInView.js -src/components/structures/login/ForgotPassword.js src/components/structures/MessagePanel.js src/components/structures/NotificationPanel.js src/components/structures/RoomDirectory.js @@ -15,6 +13,11 @@ src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/TimelinePanel.js src/components/structures/UploadBar.js +src/components/views/auth/CountryDropdown.js +src/components/views/auth/InteractiveAuthEntryComponents.js +src/components/views/auth/PasswordLogin.js +src/components/views/auth/RegistrationForm.js +src/components/views/auth/ServerConfig.js src/components/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js @@ -31,11 +34,6 @@ src/components/views/elements/UserSelector.js src/components/views/globals/MatrixToolbar.js src/components/views/globals/NewVersionBar.js src/components/views/globals/UpdateCheckBar.js -src/components/views/login/CountryDropdown.js -src/components/views/login/InteractiveAuthEntryComponents.js -src/components/views/login/PasswordLogin.js -src/components/views/login/RegistrationForm.js -src/components/views/login/ServerConfig.js src/components/views/messages/MFileBody.js src/components/views/messages/RoomAvatarEvent.js src/components/views/messages/TextualBody.js @@ -98,12 +96,12 @@ src/VectorConferenceHandler.js src/Velociraptor.js src/WhoIsTyping.js src/wrappers/withMatrixClient.js -test/components/structures/login/Registration-test.js +test/components/structures/auth/Registration-test.js test/components/structures/MessagePanel-test.js test/components/structures/ScrollPanel-test.js test/components/structures/TimelinePanel-test.js +test/components/views/auth/RegistrationForm-test.js test/components/views/dialogs/InteractiveAuthDialog-test.js -test/components/views/login/RegistrationForm-test.js test/components/views/rooms/MessageComposerInput-test.js test/components/views/rooms/RoomSettings-test.js test/mock-clock.js diff --git a/.eslintrc.js b/.eslintrc.js index 971809f851..ec48f6b2ff 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,9 +42,8 @@ module.exports = { // bind or arrow function in props causes performance issues // (but we currently use them in some places) - "react/jsx-no-bind": ["warn", { - "ignoreRefs": true, - }], + // It's disabled here, but we should using it sparingly. + "react/jsx-no-bind": "off", "react/jsx-key": ["error"], // Components in JSX should always be defined. diff --git a/karma.conf.js b/karma.conf.js index 4d699599cb..93063dbc04 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -168,6 +168,10 @@ module.exports = function (config) { path.resolve('./test'), ] }, + { + test: /\.(gif|png|svg|ttf)$/, + loader: 'file-loader', + }, ], noParse: [ // for cross platform compatibility use [\\\/] as the path separator diff --git a/package.json b/package.json index 7b55a09948..aca3d433ad 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "scripts": { "reskindex": "node scripts/reskindex.js -h header", "reskindex:watch": "node scripts/reskindex.js -h header -w", + "rethemendex": "res/css/rethemendex.sh", "i18n": "matrix-gen-i18n", "prunei18n": "matrix-prune-i18n", "build": "npm run reskindex && npm run start:init", @@ -47,7 +48,7 @@ "start:init": "babel src -d lib --source-maps --copy-files", "lint": "eslint src/", "lintall": "eslint src/ test/", - "lintwithexclusions": "eslint --max-warnings 18 --ignore-path .eslintignore.errorfiles src test", + "lintwithexclusions": "eslint --max-warnings 13 --ignore-path .eslintignore.errorfiles src test", "clean": "rimraf lib", "prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt", "test": "karma start --single-run=true --browsers ChromeHeadless", @@ -125,6 +126,7 @@ "eslint-plugin-react": "^7.7.0", "estree-walker": "^0.5.0", "expect": "^23.6.0", + "file-loader": "^3.0.1", "flow-parser": "^0.57.3", "jest-mock": "^23.2.0", "karma": "^3.0.0", diff --git a/res/css/_common.scss b/res/css/_common.scss index bec4c02c18..306834bcde 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -160,7 +160,7 @@ textarea { padding: 0 58px 36px; width: 60%; max-width: 704px; - box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2); + box-shadow: 2px 15px 30px 0 $dialog-shadow-color; max-height: 80%; overflow-y: auto; } @@ -171,7 +171,7 @@ textarea { left: 0; width: 100%; height: 100%; - background-color: $dialog-background-bg-color; + background-color: $dialog-backdrop-color; opacity: 0.8; } diff --git a/res/css/_components.scss b/res/css/_components.scss index b9df609dff..7aa10448b2 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -8,7 +8,6 @@ @import "./structures/_GroupView.scss"; @import "./structures/_HomePage.scss"; @import "./structures/_LeftPanel.scss"; -@import "./structures/_LoginBox.scss"; @import "./structures/_MatrixChat.scss"; @import "./structures/_MyGroups.scss"; @import "./structures/_NotificationPanel.scss"; @@ -18,12 +17,21 @@ @import "./structures/_RoomSubList.scss"; @import "./structures/_RoomView.scss"; @import "./structures/_SearchBox.scss"; +@import "./structures/_TabbedView.scss"; @import "./structures/_TagPanel.scss"; @import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_UserSettings.scss"; @import "./structures/_ViewSource.scss"; -@import "./structures/login/_Login.scss"; +@import "./structures/auth/_Login.scss"; +@import "./views/auth/_AuthBody.scss"; +@import "./views/auth/_AuthButtons.scss"; +@import "./views/auth/_AuthFooter.scss"; +@import "./views/auth/_AuthHeader.scss"; +@import "./views/auth/_AuthHeaderLogo.scss"; +@import "./views/auth/_AuthPage.scss"; +@import "./views/auth/_InteractiveAuthEntryComponents.scss"; +@import "./views/auth/_ServerConfig.scss"; @import "./views/avatars/_BaseAvatar.scss"; @import "./views/avatars/_MemberStatusMessageAvatar.scss"; @import "./views/context_menus/_MessageContextMenu.scss"; @@ -49,6 +57,7 @@ @import "./views/dialogs/_SetPasswordDialog.scss"; @import "./views/dialogs/_ShareDialog.scss"; @import "./views/dialogs/_UnknownDeviceDialog.scss"; +@import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss"; @import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss"; @import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss"; @@ -59,6 +68,7 @@ @import "./views/elements/_DirectorySearchBox.scss"; @import "./views/elements/_Dropdown.scss"; @import "./views/elements/_EditableItemList.scss"; +@import "./views/elements/_Field.scss"; @import "./views/elements/_HexVerify.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; @@ -75,8 +85,6 @@ @import "./views/groups/_GroupPublicityToggle.scss"; @import "./views/groups/_GroupRoomList.scss"; @import "./views/groups/_GroupUserSettings.scss"; -@import "./views/login/_InteractiveAuthEntryComponents.scss"; -@import "./views/login/_ServerConfig.scss"; @import "./views/messages/_CreateEvent.scss"; @import "./views/messages/_DateSeparator.scss"; @import "./views/messages/_MEmoteBody.scss"; @@ -95,6 +103,7 @@ @import "./views/rooms/_AuxPanel.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; +@import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_LinkPreviewWidget.scss"; @import "./views/rooms/_MemberDeviceInfo.scss"; @import "./views/rooms/_MemberInfo.scss"; @@ -122,6 +131,8 @@ @import "./views/settings/_IntegrationsManager.scss"; @import "./views/settings/_KeyBackupPanel.scss"; @import "./views/settings/_Notifications.scss"; +@import "./views/settings/tabs/_GeneralSettingsTab.scss"; +@import "./views/settings/tabs/_SettingsTab.scss"; @import "./views/voip/_CallView.scss"; @import "./views/voip/_IncomingCallbox.scss"; @import "./views/voip/_VideoView.scss"; diff --git a/res/css/structures/_MyGroups.scss b/res/css/structures/_MyGroups.scss index f9433909a5..b3a5c4f473 100644 --- a/res/css/structures/_MyGroups.scss +++ b/res/css/structures/_MyGroups.scss @@ -54,7 +54,7 @@ limitations under the License. &:before { background-color: $accent-fg-color; - mask: url('../../img/icons-create-room.svg'); + mask: url('$(res)/img/icons-create-room.svg'); mask-repeat: no-repeat; mask-position: center; mask-size: 80%; diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index b7fe19ca89..faaf1cf462 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -105,7 +105,7 @@ limitations under the License. .mx_RoomSubList_addRoom { background-color: $roomheader-addroom-color; color: $roomsublist-background; - background-image: url('../../img/icons-room-add.svg'); + background-image: url('$(res)/img/icons-room-add.svg'); background-repeat: no-repeat; background-position: center; border-radius: 10px; // 16/2 + 2 padding @@ -120,7 +120,7 @@ limitations under the License. .mx_RoomSubList_chevron { pointer-events: none; - background-image: url('../../img/topleft-chevron.svg'); + background-image: url('$(res)/img/topleft-chevron.svg'); background-repeat: no-repeat; transition: transform 0.2s ease-in; width: 10px; @@ -155,7 +155,7 @@ limitations under the License. position: sticky; left: 0; right: 0; - height: 30px; + height: 35px; content: ""; display: block; z-index: 100; @@ -163,10 +163,10 @@ limitations under the License. } &.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset { - margin-top: -30px; + margin-top: -35px; } &.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset { - margin-bottom: -30px; + margin-bottom: -35px; } &.mx_IndicatorScrollbar_topOverflow::before { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 4a84a55cd4..77b868e391 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -116,7 +116,7 @@ limitations under the License. .mx_RoomView_messagePanelSearchSpinner { flex: 1; - background-image: url('../../img/typing-indicator-2x.gif'); + background-image: url('$(res)/img/typing-indicator-2x.gif'); background-position: center 367px; background-size: 25px; background-repeat: no-repeat; @@ -125,7 +125,7 @@ limitations under the License. .mx_RoomView_messagePanelSearchSpinner:before { background-color: $greyed-fg-color; - mask: url('../../img/feather-icons/search-input.svg'); + mask: url('$(res)/img/feather-icons/search-input.svg'); mask-repeat: no-repeat; mask-position: center; mask-size: 50px; @@ -171,6 +171,10 @@ limitations under the License. .mx_RoomView_MessageList { list-style-type: none; padding: 18px; + margin: 0; + /* needed as min-height is set to clientHeight in ScrollPanel + to prevent shrinking when WhoIsTypingTile is hidden */ + box-sizing: border-box; } .mx_RoomView_MessageList li { diff --git a/res/css/structures/_SearchBox.scss b/res/css/structures/_SearchBox.scss index e559236569..9434d93bd2 100644 --- a/res/css/structures/_SearchBox.scss +++ b/res/css/structures/_SearchBox.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_SearchBox_closeButton { cursor: pointer; - background-image: url('../../img/icons-close.svg'); + background-image: url('$(res)/img/icons-close.svg'); background-repeat: no-repeat; width: 16px; height: 16px; diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss new file mode 100644 index 0000000000..0f4b67ad71 --- /dev/null +++ b/res/css/structures/_TabbedView.scss @@ -0,0 +1,92 @@ +/* +Copyright 2017 Travis Ralston +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_TabbedView { + margin: 0; + padding: 0; + display: flex; + width: 100%; + height: 100%; +} + +.mx_TabbedView_tabLabels { + width: 136px; + height: 100%; + color: $tab-label-fg-color; +} + +.mx_TabbedView_tabLabel { + vertical-align: text-top; + cursor: pointer; + display: block; + border-radius: 3px; + font-size: 12px; + font-weight: 600; + height: 20px; + margin-bottom: 6px; + position: relative; +} + +.mx_TabbedView_tabLabel_active { + background-color: $tab-label-active-bg-color; + color: $tab-label-active-fg-color; +} + +// TODO: Remove temporary hack alongside "visit old settings" tab +.mx_TabbedView_tabLabel_TEMP_HACK { + background-color: orange; +} + +.mx_TabbedView_maskedIcon {; + margin-left: 6px; + margin-right: 9px; + width: 14px; + height: 14px; + display: inline-block; +} + +.mx_TabbedView_maskedIcon:before { + display: inline-block; + background-color: $tab-label-icon-bg-color; + mask-repeat: no-repeat; + mask-size: 14px; + width: 14px; + height: 14px; + mask-position: center; + content: ''; + vertical-align: middle; +} + +.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon:before { + background-color: $tab-label-active-icon-bg-color; +} + +.mx_TabbedView_tabLabel_text { + vertical-align: middle; +} + +.mx_TabbedView_tabPanel { + width: calc(100% - 320px); + display: inline-block; + margin-left: 70px; + flex-grow: 1; +} + +.mx_TabbedView_tabPanelContent { + flex-grow: 1; + min-width: 560px; +} \ No newline at end of file diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index 77eefc7e10..bf1746bd0e 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -130,12 +130,12 @@ limitations under the License. } .mx_TagPanel_groupsButton > .mx_GroupsButton:before { - mask: url('../../img/feather-icons/users.svg'); + mask: url('$(res)/img/feather-icons/users.svg'); mask-position: center 11px; } .mx_TagPanel_groupsButton > .mx_TagPanel_report:before { - mask: url('../../img/feather-icons/life-buoy.svg'); + mask: url('$(res)/img/feather-icons/life-buoy.svg'); mask-position: center 9px; } diff --git a/res/css/structures/_UserSettings.scss b/res/css/structures/_UserSettings.scss index 6fae8d6c1a..74d8c2c718 100644 --- a/res/css/structures/_UserSettings.scss +++ b/res/css/structures/_UserSettings.scss @@ -173,7 +173,7 @@ limitations under the License. padding: 0px; width: 240px; color: $input-fg-color; - font-family: 'Open Sans', Helvetica, Arial, Sans-Serif; + font-family: $font-family; font-size: 16px; } diff --git a/res/css/structures/login/_Login.scss b/res/css/structures/auth/_Login.scss similarity index 86% rename from res/css/structures/login/_Login.scss rename to res/css/structures/auth/_Login.scss index 1264d2a30f..c4eebc48a1 100644 --- a/res/css/structures/login/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,41 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_Login { - width: 100%; - height: 100%; - - display: flex; - align-items: center; - justify-content: center; - - overflow: auto; -} - -.mx_Login h2 { - font-weight: 300; - margin-top: 32px; - margin-bottom: 20px; -} - -.mx_Login_box { - width: 300px; - min-height: 450px; - padding-top: 50px; - padding-bottom: 50px; - margin: auto; -} - -.mx_Login_logo { - text-align: center; - height: 150px; - margin-bottom: 45px; -} - -.mx_Login_logo img { - max-height: 100% -} - .mx_Login_support { text-align: center; font-size: 13px; @@ -115,19 +81,6 @@ limitations under the License. color: $primary-fg-color; } -.mx_Login_links { - display: block; - text-align: center; - margin-top: 15px; - width: 100%; - font-size: 13px; - opacity: 0.8; -} - -.mx_Login_links a:link { - color: $primary-fg-color; -} - .mx_Login_prompt { padding-top: 15px; padding-bottom: 15px; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss new file mode 100644 index 0000000000..97632c2e3f --- /dev/null +++ b/res/css/views/auth/_AuthBody.scss @@ -0,0 +1,23 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_AuthBody { + width: 500px; + background-color: $authpage-body-bg-color; + border-radius: 0 4px 4px 0; + padding: 25px 60px; + box-sizing: border-box; +} diff --git a/res/css/structures/_LoginBox.scss b/res/css/views/auth/_AuthButtons.scss similarity index 87% rename from res/css/structures/_LoginBox.scss rename to res/css/views/auth/_AuthButtons.scss index 0a3e21a980..553adeee14 100644 --- a/res/css/structures/_LoginBox.scss +++ b/res/css/views/auth/_AuthButtons.scss @@ -1,5 +1,6 @@ /* Copyright 2017 OpenMarket Ltd +Copyright 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_LoginBox { +.mx_AuthButtons { min-height: 24px; height: unset !important; padding-top: 13px !important; @@ -22,13 +23,13 @@ limitations under the License. order: 4; } -.mx_LoginBox_loginButton_wrapper { +.mx_AuthButtons_loginButton_wrapper { text-align: center; width: 100%; } -.mx_LoginBox_loginButton, -.mx_LoginBox_registerButton { +.mx_AuthButtons_loginButton, +.mx_AuthButtons_registerButton { margin-top: 3px; height: 40px; border: 0px; diff --git a/res/css/views/auth/_AuthFooter.scss b/res/css/views/auth/_AuthFooter.scss new file mode 100644 index 0000000000..741be49a69 --- /dev/null +++ b/res/css/views/auth/_AuthFooter.scss @@ -0,0 +1,30 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_AuthFooter { + text-align: center; + width: 100%; + font-size: 14px; + opacity: 0.72; + margin: 20px 0; +} + +.mx_AuthFooter a:link, +.mx_AuthFooter a:hover, +.mx_AuthFooter a:visited { + color: $accent-fg-color; + margin: 0 22px; +} diff --git a/res/css/views/auth/_AuthHeader.scss b/res/css/views/auth/_AuthHeader.scss new file mode 100644 index 0000000000..376864d268 --- /dev/null +++ b/res/css/views/auth/_AuthHeader.scss @@ -0,0 +1,21 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_AuthHeader { + width: 206px; + padding: 25px 50px; + box-sizing: border-box; +} diff --git a/res/css/views/auth/_AuthHeaderLogo.scss b/res/css/views/auth/_AuthHeaderLogo.scss new file mode 100644 index 0000000000..3d8ab29325 --- /dev/null +++ b/res/css/views/auth/_AuthHeaderLogo.scss @@ -0,0 +1,23 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_AuthHeaderLogo { + margin-top: 15px; +} + +.mx_AuthHeaderLogo img { + width: 100%; +} diff --git a/res/css/views/auth/_AuthPage.scss b/res/css/views/auth/_AuthPage.scss new file mode 100644 index 0000000000..9e32750771 --- /dev/null +++ b/res/css/views/auth/_AuthPage.scss @@ -0,0 +1,40 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_AuthPage { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: auto; + background-color: $authpage-bg-color; +} + +.mx_AuthPage h2 { + font-weight: 300; + margin-top: 32px; + margin-bottom: 20px; +} + +.mx_AuthPage_modal { + display: flex; + margin: 100px auto auto; + border-radius: 4px; + // Not currently supported in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1178765 + backdrop-filter: blur(10px); + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.33); + background-color: $authpage-modal-bg-color; +} diff --git a/res/css/views/login/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss similarity index 100% rename from res/css/views/login/_InteractiveAuthEntryComponents.scss rename to res/css/views/auth/_InteractiveAuthEntryComponents.scss diff --git a/res/css/views/login/_ServerConfig.scss b/res/css/views/auth/_ServerConfig.scss similarity index 100% rename from res/css/views/login/_ServerConfig.scss rename to res/css/views/auth/_ServerConfig.scss diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index a4a868bd11..572d6ee8c7 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -61,7 +61,7 @@ limitations under the License. padding: 0; width: 240px; color: $input-fg-color; - font-family: 'Open Sans', Helvetica, Arial, Sans-Serif; + font-family: $font-family; font-size: 16px; } diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss new file mode 100644 index 0000000000..c4bd8a5110 --- /dev/null +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -0,0 +1,63 @@ +.mx_UserSettingsDialog_header { + font-size: 24px; + display: block; + text-align: center; + color: $dialog-title-fg-color; + margin-top: 16px; + margin-bottom: 24px; + padding: 0; +} + +.mx_UserSettingsDialog_close { + position: absolute; + top: 16px; + right: 25px; +} + +.mx_UserSettingsDialog_closeIcon { + width: 16px; + height: 16px; + display: inline-block; +} + +.mx_UserSettingsDialog_closeIcon:before { + mask: url('$(res)/img/feather-icons/cancel.svg'); + background-color: $dialog-close-fg-color; + mask-repeat: no-repeat; + mask-size: 16px; + mask-position: center; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + + +// ICONS +// ========================================================== + +.mx_UserSettingsDialog_settingsIcon:before { + mask-image: url('$(res)/img/feather-icons/settings.svg'); +} + +.mx_UserSettingsDialog_voiceIcon:before { + mask-image: url('$(res)/img/feather-icons/phone.svg'); +} + +.mx_UserSettingsDialog_bellIcon:before { + mask-image: url('$(res)/img/feather-icons/notifications.svg'); +} + +.mx_UserSettingsDialog_preferencesIcon:before { + mask-image: url('$(res)/img/feather-icons/sliders.svg'); +} + +.mx_UserSettingsDialog_securityIcon:before { + mask-image: url('$(res)/img/feather-icons/lock.svg'); +} + +.mx_UserSettingsDialog_helpIcon:before { + mask-image: url('$(res)/img/feather-icons/help-circle.svg'); +} \ No newline at end of file diff --git a/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss b/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss index 4a050b6fc4..bd703badaa 100644 --- a/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss +++ b/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss @@ -24,7 +24,7 @@ limitations under the License. padding-bottom: 10px; &:before { - mask: url("../../img/e2e/lock-warning.svg"); + mask: url("$(res)/img/e2e/lock-warning.svg"); mask-repeat: no-repeat; background-color: $primary-fg-color; content: ""; diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index d6702a232c..23445f5f6f 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -21,3 +21,24 @@ limitations under the License. .mx_AccessibleButton { cursor: pointer; } + +.mx_AccessibleButton_disabled { + cursor: default; +} + +.mx_AccessibleButton_hasKind { + padding: 10px 25px; + text-align: center; + border-radius: 4px; + display: inline-block; +} + +.mx_AccessibleButton_kind_primary { + color: $button-primary-fg-color; + background-color: $button-primary-bg-color; +} + +.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled { + color: $button-primary-disabled-fg-color; + background-color: $button-primary-disabled-bg-color; +} diff --git a/res/css/views/elements/_DirectorySearchBox.scss b/res/css/views/elements/_DirectorySearchBox.scss index 94a92b23ce..1a8c2b93de 100644 --- a/res/css/views/elements/_DirectorySearchBox.scss +++ b/res/css/views/elements/_DirectorySearchBox.scss @@ -44,7 +44,7 @@ input[type=text].mx_DirectorySearchBox_input:focus { padding-right: 10px; background-color: $plinth-bg-color; border-radius: 3px; - background-image: url('../../img/icon-return.svg'); + background-image: url('$(res)/img/icon-return.svg'); background-position: 8px 70%; background-repeat: no-repeat; text-indent: 18px; @@ -61,7 +61,7 @@ input[type=text].mx_DirectorySearchBox_input:focus { .mx_DirectorySearchBox_clear { display: inline-block; vertical-align: middle; - background: url('../../img/icon_context_delete.svg'); + background: url('$(res)/img/icon_context_delete.svg'); background-position: 0 50%; background-repeat: no-repeat; width: 15px; diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss new file mode 100644 index 0000000000..4f6e868249 --- /dev/null +++ b/res/css/views/elements/_Field.scss @@ -0,0 +1,81 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* TODO: Consider unifying with general input styles in _dharma.scss */ + +.mx_Field { + position: relative; + margin: 1em 0; +} + +.mx_Field input, +.mx_Field select { + font-weight: normal; + border-radius: 4px; + transition: border-color 0.25s; + border: 1px solid $input-border-color; + padding: 8px 9px; +} + +.mx_Field input:focus, +.mx_Field select:focus { + outline: 0; + border-color: $input-focused-border-color; +} + +.mx_Field input::placeholder { + transition: color 0.25s ease-in 0s; + color: transparent; +} + +.mx_Field input:placeholder-shown:focus::placeholder { + transition: color 0.25s ease-in 0.1s; + color: $greyed-fg-color; +} + +.mx_Field label { + transition: + font-size 0.25s ease-out 0.1s, + color 0.25s ease-out 0.1s, + top 0.25s ease-out 0.1s, + background-color 0.25s ease-out 0.1s; + color: $primary-fg-color; + background-color: transparent; + font-size: 14px; + position: absolute; + left: 0px; + top: 0px; + margin: 7px 8px; + padding: 2px; +} + +.mx_Field input:focus + label, +.mx_Field input:not(:placeholder-shown) + label, +.mx_Field select:focus + label { + transition: + font-size 0.25s ease-out 0s, + color 0.25s ease-out 0s, + top 0.25s ease-out 0s, + background-color 0.25s ease-out 0s; + font-size: 10px; + top: -14px; + background-color: $field-focused-label-bg-color; +} + +.mx_Field input:focus + label, +.mx_Field select:focus + label { + color: $input-focused-border-color; +} diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 8ed0698a72..9bf23b1025 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -50,7 +50,7 @@ limitations under the License. max-height: 100%; /* object-fit hack needed for Chrome due to Chrome not re-laying-out until you refresh */ object-fit: contain; - /* background-image: url('../../img/trans.png'); */ + /* background-image: url('$(res)/img/trans.png'); */ pointer-events: all; } diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss index c4d4d944a6..80b8126b54 100644 --- a/res/css/views/rooms/_EntityTile.scss +++ b/res/css/views/rooms/_EntityTile.scss @@ -22,7 +22,7 @@ limitations under the License. } .mx_EntityTile:hover { - background-image: url('../../img/member_chevron.png'); + background-image: url('$(res)/img/member_chevron.png'); background-position: center right 10px; background-repeat: no-repeat; padding-right: 30px; diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 7fb6812c79..ee9971887e 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -291,8 +291,8 @@ limitations under the License. } /* always override hidden attribute for blocked and warning */ -.mx_EventTile_e2eIcon_hidden[src="img/e2e-blocked.svg"], -.mx_EventTile_e2eIcon_hidden[src="img/e2e-warning.svg"] { +.mx_EventTile_e2eIcon_hidden[src*="img/e2e-blocked.svg"], +.mx_EventTile_e2eIcon_hidden[src*="img/e2e-warning.svg"] { display: block; } diff --git a/res/css/views/rooms/_JumpToBottomButton.scss b/res/css/views/rooms/_JumpToBottomButton.scss new file mode 100644 index 0000000000..968139671f --- /dev/null +++ b/res/css/views/rooms/_JumpToBottomButton.scss @@ -0,0 +1,69 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +@charset "utf-8"; + +.mx_JumpToBottomButton { + z-index: 1000; + position: absolute; + // 12 because height is 50 but button is only 38 = 12+(50-38) = 24 + bottom: 12px; + right: 24px; + width: 38px; + // give it a fixed height so the badge doesn't make + // it taller and pop upwards when visible + height: 50px; + text-align: center; +} + +.mx_JumpToBottomButton_badge { + position: relative; + top: -12px; + border-radius: 16px; + font-weight: bold; + font-size: 12px; + line-height: 14px; + text-align: center; + // to be able to get it centered + // with text-align in parent + display: inline-block; + padding: 0 4px; + color: $secondary-accent-color; + background-color: $warning-color; +} + +.mx_JumpToBottomButton_scrollDown { + position: relative; + height: 38px; + border-radius: 19px; + box-sizing: border-box; + background: $primary-bg-color; + border: 1.3px solid $roomtile-name-color; + cursor: pointer; +} + +.mx_JumpToBottomButton_scrollDown:before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + mask: url('$(res)/img/icon-jump-to-bottom.svg'); + mask-repeat: no-repeat; + mask-position: 9px 14px; + background: $roomtile-name-color; +} diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 6f9491b22f..ab3b68b346 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -83,7 +83,7 @@ limitations under the License. .mx_MemberList_invite span { margin: 0 auto; - background-image: url('../../img/feather-icons/user-add.svg'); + background-image: url('$(res)/img/feather-icons/user-add.svg'); background-repeat: no-repeat; background-position: center left; padding-left: 25px; diff --git a/res/css/views/rooms/_RoomSettings.scss b/res/css/views/rooms/_RoomSettings.scss index 4454cd479c..5ed9168aab 100644 --- a/res/css/views/rooms/_RoomSettings.scss +++ b/res/css/views/rooms/_RoomSettings.scss @@ -66,15 +66,16 @@ limitations under the License. .mx_RoomSettings_integrationsButton_errorPopup { position: absolute; top: 110%; - left: -125%; - width: 348%; - padding: 2%; + left: -275%; + width: 550%; + padding: 30%; font-size: 10pt; line-height: 1.5em; border-radius: 5px; background-color: $accent-color; color: $accent-fg-color; text-align: center; + z-index: 1000; } .mx_RoomSettings_unbanButton { display: inline; diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 56ca715b51..395dcd68d9 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -29,7 +29,7 @@ limitations under the License. display: none; flex: 0 0 16px; height: 16px; - background-image: url('../../img/icon_context.svg'); + background-image: url('$(res)/img/icon_context.svg'); background-repeat: no-repeat; background-position: center; } diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index 390d6606c1..b89cb0ce13 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -32,7 +32,7 @@ limitations under the License. width: 37px; height: 37px; background-color: $accent-color; - mask: url('../../img/feather-icons/search-input.svg'); + mask: url('$(res)/img/feather-icons/search-input.svg'); mask-repeat: no-repeat; mask-position: center; } @@ -55,7 +55,7 @@ limitations under the License. .mx_SearchBar_cancel { background-color: $warning-color; - mask: url('../../img/cancel.svg'); + mask: url('$(res)/img/cancel.svg'); mask-repeat: no-repeat; mask-position: center; mask-size: 14px; diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.scss b/res/css/views/rooms/_TopUnreadMessagesBar.scss index 67579552c1..a4b7a6aa51 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.scss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.scss @@ -54,7 +54,7 @@ limitations under the License. position: absolute; width: 38px; height: 38px; - mask: url('../../img/icon-jump-to-first-unread.svg'); + mask: url('$(res)/img/icon-jump-to-first-unread.svg'); mask-repeat: no-repeat; mask-position: 9px 13px; background: $roomtile-name-color; diff --git a/res/css/views/rooms/_WhoIsTypingTile.scss b/res/css/views/rooms/_WhoIsTypingTile.scss index 217a10be8d..eb51595858 100644 --- a/res/css/views/rooms/_WhoIsTypingTile.scss +++ b/res/css/views/rooms/_WhoIsTypingTile.scss @@ -61,7 +61,7 @@ limitations under the License. } .mx_WhoIsTypingTile_label > span { - background-image: url('../../img/typing-indicator-2x.gif'); + background-image: url('$(res)/img/typing-indicator-2x.gif'); background-size: 25px; background-position: left bottom; background-repeat: no-repeat; diff --git a/res/css/views/settings/tabs/_GeneralSettingsTab.scss b/res/css/views/settings/tabs/_GeneralSettingsTab.scss new file mode 100644 index 0000000000..8bc3de8689 --- /dev/null +++ b/res/css/views/settings/tabs/_GeneralSettingsTab.scss @@ -0,0 +1,25 @@ +.mx_GeneralSettingsTab_profile { + display: flex; +} + +.mx_GeneralSettingsTab_profileControls { + flex-grow: 1; +} + +.mx_GeneralSettingsTab_profileControls .mx_Field #profileDisplayName { + width: calc(100% - 20px); // subtract 10px padding on left and right +} + +.mx_GeneralSettingsTab_profileAvatar { + width: 88px; + height: 88px; + margin-left: 13px; +} + +.mx_GeneralSettingsTab_profileAvatar div { + display: block; + width: 88px; + height: 88px; + border-radius: 4px; + background-color: #ccc; +} \ No newline at end of file diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss new file mode 100644 index 0000000000..0753df56af --- /dev/null +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -0,0 +1,17 @@ +.mx_SettingsTab_heading { + font-size: 20px; + font-weight: 600; + color: $primary-fg-color; +} + +.mx_SettingsTab_subheading { + font-size: 14px; + display: block; + font-family: $font-family-semibold; + color: $primary-fg-color; + margin-bottom: 10px; +} + +.mx_SettingsTab_section { + margin-top: 10px; +} diff --git a/res/img/feather-icons/cancel.svg b/res/img/feather-icons/cancel.svg new file mode 100644 index 0000000000..6b734e4053 --- /dev/null +++ b/res/img/feather-icons/cancel.svg @@ -0,0 +1,10 @@ + + + + Slice 1 + Created with Sketch. + + + + + \ No newline at end of file diff --git a/res/img/feather-icons/help-circle.svg b/res/img/feather-icons/help-circle.svg new file mode 100644 index 0000000000..7ecb0a8f35 --- /dev/null +++ b/res/img/feather-icons/help-circle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/feather-icons/lock.svg b/res/img/feather-icons/lock.svg new file mode 100644 index 0000000000..1330903b30 --- /dev/null +++ b/res/img/feather-icons/lock.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/feather-icons/sliders.svg b/res/img/feather-icons/sliders.svg new file mode 100644 index 0000000000..5b5ec8656c --- /dev/null +++ b/res/img/feather-icons/sliders.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/icon-jump-to-bottom.svg b/res/img/icon-jump-to-bottom.svg new file mode 100644 index 0000000000..c4210b4ebe --- /dev/null +++ b/res/img/icon-jump-to-bottom.svg @@ -0,0 +1,32 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 997a74e6aa..0afa6a91e1 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -149,8 +149,8 @@ $event-redacted-border-color: #000000; // event timestamp $event-timestamp-color: #acacac; -$edit-button-url: "../../img/icon_context_message_dark.svg"; -$copy-button-url: "../../img/icon_copy_message_dark.svg"; +$edit-button-url: "$(res)/img/icon_context_message_dark.svg"; +$copy-button-url: "$(res)/img/icon_copy_message_dark.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color diff --git a/res/themes/dark/css/dark.scss b/res/themes/dark/css/dark.scss index b69f096db7..a7d18fa1a6 100644 --- a/res/themes/dark/css/dark.scss +++ b/res/themes/dark/css/dark.scss @@ -1,4 +1,5 @@ +@import "../../light/css/_paths.scss"; +@import "../../light/css/_fonts.scss"; @import "../../light/css/_base.scss"; @import "_dark.scss"; @import "../../../../res/css/_components.scss"; - diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss index c70d7f020a..235eb02e3c 100644 --- a/res/themes/dharma/css/_dharma.scss +++ b/res/themes/dharma/css/_dharma.scss @@ -1,5 +1,3 @@ -@import "_fonts.scss"; - // XXX: check this? /* Nunito lacks combining diacritics, so these will fall through to the next font. Helevetica's diacritics however do not combine @@ -7,6 +5,7 @@ horizontal mess. Arial empirically gets it right, hence prioritising Arial here. */ $font-family: 'Nunito', Arial, Helvetica, Sans-Serif; +$font-family-semibold: 'Nunito SemiBold', Arial, Helvetica, Sans-Serif; // typical text (dark-on-white in light skin) $primary-fg-color: #454545; @@ -73,6 +72,10 @@ $input-darker-bg-color: rgba(193, 201, 214, 0.29); $input-darker-fg-color: #9fa9ba; $input-lighter-bg-color: #f2f5f8; $input-lighter-fg-color: $input-darker-fg-color; +$input-focused-border-color: #238cf5; +$input-valid-border-color: #7ac9a1; + +$field-focused-label-bg-color: #ffffff; $button-bg-color: #7ac9a1; $button-fg-color: white; @@ -94,6 +97,11 @@ $avatar-bg-color: #ffffff; $h3-color: #3d3b39; +$dialog-title-fg-color: #454545; +$dialog-backdrop-color: rgba(46, 48, 51, 0.38); +$dialog-shadow-color: rgba(0, 0, 0, 0.48); +$dialog-close-fg-color: #9fa9ba; + $dialog-background-bg-color: #e9e9e9; $lightbox-background-bg-color: #000; @@ -171,8 +179,8 @@ $event-redacted-border-color: #cccccc; // event timestamp $event-timestamp-color: #acacac; -$edit-button-url: "../../img/icon_context_message.svg"; -$copy-button-url: "../../img/icon_copy_message.svg"; +$edit-button-url: "$(res)/img/icon_context_message.svg"; +$copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color @@ -184,6 +192,20 @@ $lightbox-bg-color: #454545; $lightbox-fg-color: #ffffff; $lightbox-border-color: #ffffff; +// Tabbed views +$tab-label-fg-color: #45474a; +$tab-label-active-fg-color: #ffffff; +$tab-label-bg-color: transparent; +$tab-label-active-bg-color: #7ac9a1; +$tab-label-icon-bg-color: #454545; +$tab-label-active-icon-bg-color: #ffffff; + +// Buttons +$button-primary-fg-color: #ffffff; +$button-primary-bg-color: #7ac9a1; +$button-primary-disabled-fg-color: #ffffff; +$button-primary-disabled-bg-color: #bce4d0; + // unused? $progressbar-color: #000; @@ -191,6 +213,10 @@ $room-warning-bg-color: #fff8e3; $memberstatus-placeholder-color: $roomtile-name-color; +$authpage-bg-color: #2e3649; +$authpage-modal-bg-color: rgba(255, 255, 255, 0.59); +$authpage-body-bg-color: #ffffff; + /*** form elements ***/ // .mx_textinput is a container for a text input @@ -200,8 +226,8 @@ $memberstatus-placeholder-color: $roomtile-name-color; .mx_MatrixChat { - :not(.mx_textinput) > input[type=text], - :not(.mx_textinput) > input[type=search], + :not(.mx_textinput):not(.mx_Field) > input[type=text], + :not(.mx_textinput):not(.mx_Field) > input[type=search], .mx_textinput { display: block; margin: 9px; @@ -245,8 +271,8 @@ input[type=password] { } .dark-panel { - :not(.mx_textinput) > input[type=text], - :not(.mx_textinput) > input[type=search], + :not(.mx_textinput):not(.mx_Field) > input[type=text], + :not(.mx_textinput):not(.mx_Field) > input[type=search], .mx_textinput { color: $input-darker-fg-color; background-color: $input-darker-bg-color; @@ -255,8 +281,8 @@ input[type=password] { } .light-panel { - :not(.mx_textinput) > input[type=text], - :not(.mx_textinput) > input[type=search], + :not(.mx_textinput):not(.mx_Field) > input[type=text], + :not(.mx_textinput):not(.mx_Field) > input[type=search], .mx_textinput { color: $input-lighter-fg-color; background-color: $input-lighter-bg-color; @@ -275,7 +301,7 @@ input[type=search].mx_textinput_icon { // FIXME THEME - Tint by CSS rather than referencing a duplicate asset input[type=text].mx_textinput_icon.mx_textinput_search, input[type=search].mx_textinput_icon.mx_textinput_search { - background-image: url('../../img/feather-icons/search-input.svg'); + background-image: url('$(res)/img/feather-icons/search-input.svg'); } // dont search UI as not all browsers support it, @@ -293,6 +319,13 @@ input[type=search]::-webkit-search-results-decoration { .input[type=search]::-moz-placeholder { color: #a5aab2; } + +// Override Firefox's UA style so we get a consistent look across browsers +input::placeholder, +textarea::placeholder { + opacity: initial; +} + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/dharma/css/_fonts.scss b/res/themes/dharma/css/_fonts.scss index bb45432262..93fa9afbfa 100644 --- a/res/themes/dharma/css/_fonts.scss +++ b/res/themes/dharma/css/_fonts.scss @@ -11,37 +11,37 @@ font-family: 'Nunito'; font-style: italic; font-weight: 400; - src: url('../../fonts/Nunito/XRXX3I6Li01BKofIMNaDRss.ttf') format('truetype'); + src: url('$(res)/fonts/Nunito/XRXX3I6Li01BKofIMNaDRss.ttf') format('truetype'); } @font-face { font-family: 'Nunito'; font-style: italic; font-weight: 600; - src: url('../../fonts/Nunito/XRXQ3I6Li01BKofIMN5cYtvKUTo.ttf') format('truetype'); + src: url('$(res)/fonts/Nunito/XRXQ3I6Li01BKofIMN5cYtvKUTo.ttf') format('truetype'); } @font-face { font-family: 'Nunito'; font-style: italic; font-weight: 700; - src: url('../../fonts/Nunito/XRXQ3I6Li01BKofIMN44Y9vKUTo.ttf') format('truetype'); + src: url('$(res)/fonts/Nunito/XRXQ3I6Li01BKofIMN44Y9vKUTo.ttf') format('truetype'); } @font-face { font-family: 'Nunito'; font-style: normal; font-weight: 400; - src: url('../../fonts/Nunito/XRXV3I6Li01BKofINeaE.ttf') format('truetype'); + src: url('$(res)/fonts/Nunito/XRXV3I6Li01BKofINeaE.ttf') format('truetype'); } @font-face { font-family: 'Nunito'; font-style: normal; font-weight: 600; - src: url('../../fonts/Nunito/XRXW3I6Li01BKofA6sKUYevN.ttf') format('truetype'); + src: url('$(res)/fonts/Nunito/XRXW3I6Li01BKofA6sKUYevN.ttf') format('truetype'); } @font-face { font-family: 'Nunito'; font-style: normal; font-weight: 700; - src: url('../../fonts/Nunito/XRXW3I6Li01BKofAjsOUYevN.ttf') format('truetype'); + src: url('$(res)/fonts/Nunito/XRXW3I6Li01BKofAjsOUYevN.ttf') format('truetype'); } /* @@ -51,14 +51,14 @@ @font-face { font-family: 'Fira Mono'; - src: url('../../fonts/Fira_Mono/FiraMono-Regular.ttf') format('truetype'); + src: url('$(res)/fonts/Fira_Mono/FiraMono-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Fira Mono'; - src: url('../../fonts/Fira_Mono/FiraMono-Bold.ttf') format('truetype'); + src: url('$(res)/fonts/Fira_Mono/FiraMono-Bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } diff --git a/res/themes/dharma/css/dharma.scss b/res/themes/dharma/css/dharma.scss index 0f4db55fd2..24dc0ce18d 100644 --- a/res/themes/dharma/css/dharma.scss +++ b/res/themes/dharma/css/dharma.scss @@ -1,3 +1,4 @@ +@import "../../light/css/_paths.scss"; +@import "_fonts.scss"; @import "_dharma.scss"; @import "../../../../res/css/_components.scss"; - diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss index 96c179f6f5..1522ee82a0 100644 --- a/res/themes/light/css/_base.scss +++ b/res/themes/light/css/_base.scss @@ -1,11 +1,10 @@ -@import "_fonts.scss"; - /* Open Sans lacks combining diacritics, so these will fall through to the next font. Helevetica's diacritics however do not combine nicely with Open Sans (on OSX, at least) and result in a huge horizontal mess. Arial empirically gets it right, hence prioritising Arial here. */ $font-family: 'Open Sans', Arial, Helvetica, Sans-Serif; +$font-family-semibold: 'Open Sans', Arial, Helvetica, Sans-Serif; // typical text (dark-on-white in light skin) $primary-fg-color: #454545; @@ -68,6 +67,7 @@ $primary-hairline-color: #e5e5e5; // used for the border of input text fields $input-border-color: #f0f0f0; +$input-border-dark-color: #b8b8b8; $input-darker-bg-color: #c1c9d6; $input-darker-fg-color: #9fa9ba; @@ -75,6 +75,10 @@ $button-bg-color: #7ac9a1; $button-fg-color: white; // apart from login forms, which have stronger border $strong-input-border-color: #c7c7c7; +$input-focused-border-color: #238cf5; +$input-valid-border-color: #7ac9a1; + +$field-focused-label-bg-color: #ffffff; // used for UserSettings EditableText $input-underline-color: rgba(151, 151, 151, 0.5); @@ -90,6 +94,11 @@ $avatar-bg-color: #ffffff; $h3-color: #3d3b39; +$dialog-title-fg-color: #454545; +$dialog-backdrop-color: rgba(46, 48, 51, 0.38); +$dialog-shadow-color: rgba(0, 0, 0, 0.48); +$dialog-close-fg-color: #9fa9ba; + $dialog-background-bg-color: #e9e9e9; $lightbox-background-bg-color: #000; @@ -162,8 +171,8 @@ $event-redacted-border-color: #cccccc; // event timestamp $event-timestamp-color: #acacac; -$edit-button-url: "../../img/icon_context_message.svg"; -$copy-button-url: "../../img/icon_copy_message.svg"; +$edit-button-url: "$(res)/img/icon_context_message.svg"; +$copy-button-url: "$(res)/img/icon_copy_message.svg"; // e2e $e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color @@ -179,6 +188,20 @@ $imagebody-giflabel: rgba(0, 0, 0, 0.7); $imagebody-giflabel-border: rgba(0, 0, 0, 0.2); $imagebody-giflabel-color: rgba(255, 255, 255, 1); +// Tabbed views +$tab-label-fg-color: #45474a; +$tab-label-active-fg-color: #ffffff; +$tab-label-bg-color: transparent; +$tab-label-active-bg-color: #7ac9a1; +$tab-label-icon-bg-color: #454545; +$tab-label-active-icon-bg-color: #ffffff; + +// Buttons +$button-primary-fg-color: #ffffff; +$button-primary-bg-color: #7ac9a1; +$button-primary-disabled-fg-color: #ffffff; +$button-primary-disabled-bg-color: #bce4d0; + // unused? $progressbar-color: #000; @@ -186,6 +209,10 @@ $room-warning-bg-color: #fff8e3; $memberstatus-placeholder-color: $roomtile-name-color; +$authpage-bg-color: #2e3649; +$authpage-modal-bg-color: rgba(255, 255, 255, 0.59); +$authpage-body-bg-color: #ffffff; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_fonts.scss b/res/themes/light/css/_fonts.scss index 52ac95b569..c080663acf 100644 --- a/res/themes/light/css/_fonts.scss +++ b/res/themes/light/css/_fonts.scss @@ -7,42 +7,42 @@ */ @font-face { font-family: 'Open Sans'; - src: url('../../fonts/Open_Sans/OpenSans-Regular.ttf') format('truetype'); + src: url('$(res)/fonts/Open_Sans/OpenSans-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Open Sans'; - src: url('../../fonts/Open_Sans/OpenSans-Italic.ttf') format('truetype'); + src: url('$(res)/fonts/Open_Sans/OpenSans-Italic.ttf') format('truetype'); font-weight: 400; font-style: italic; } @font-face { font-family: 'Open Sans'; - src: url('../../fonts/Open_Sans/OpenSans-Semibold.ttf') format('truetype'); + src: url('$(res)/fonts/Open_Sans/OpenSans-Semibold.ttf') format('truetype'); font-weight: 600; font-style: normal; } @font-face { font-family: 'Open Sans'; - src: url('../../fonts/Open_Sans/OpenSans-SemiboldItalic.ttf') format('truetype'); + src: url('$(res)/fonts/Open_Sans/OpenSans-SemiboldItalic.ttf') format('truetype'); font-weight: 600; font-style: italic; } @font-face { font-family: 'Open Sans'; - src: url('../../fonts/Open_Sans/OpenSans-Bold.ttf') format('truetype'); + src: url('$(res)/fonts/Open_Sans/OpenSans-Bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } @font-face { font-family: 'Open Sans'; - src: url('../../fonts/Open_Sans/OpenSans-BoldItalic.ttf') format('truetype'); + src: url('$(res)/fonts/Open_Sans/OpenSans-BoldItalic.ttf') format('truetype'); font-weight: 700; font-style: italic; } @@ -54,14 +54,14 @@ @font-face { font-family: 'Fira Mono'; - src: url('../../fonts/Fira_Mono/FiraMono-Regular.ttf') format('truetype'); + src: url('$(res)/fonts/Fira_Mono/FiraMono-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Fira Mono'; - src: url('../../fonts/Fira_Mono/FiraMono-Bold.ttf') format('truetype'); + src: url('$(res)/fonts/Fira_Mono/FiraMono-Bold.ttf') format('truetype'); font-weight: 700; font-style: normal; } diff --git a/res/themes/light/css/_paths.scss b/res/themes/light/css/_paths.scss new file mode 100644 index 0000000000..0744347826 --- /dev/null +++ b/res/themes/light/css/_paths.scss @@ -0,0 +1,3 @@ +// Path from root SCSS file (such as `light.scss`) to `res` dir in the source tree +// This value is overridden by external themes in `riot-web`. +$res: ../../..; diff --git a/res/themes/light/css/light.scss b/res/themes/light/css/light.scss index 2099f41f60..6ac8d0feba 100644 --- a/res/themes/light/css/light.scss +++ b/res/themes/light/css/light.scss @@ -1,3 +1,4 @@ +@import "_paths.scss"; +@import "_fonts.scss"; @import "_base.scss"; @import "../../../../res/css/_components.scss"; - diff --git a/src/Avatar.js b/src/Avatar.js index d41a3f6a79..d3df12eb49 100644 --- a/src/Avatar.js +++ b/src/Avatar.js @@ -56,6 +56,6 @@ module.exports = { for (let i = 0; i < s.length; ++i) { total += s.charCodeAt(i); } - return 'img/' + images[total % images.length] + '.png'; + return require('../res/img/' + images[total % images.length] + '.png'); }, }; diff --git a/src/Rooms.js b/src/Rooms.js index 6f73ea0659..c8f90ec39a 100644 --- a/src/Rooms.js +++ b/src/Rooms.js @@ -159,6 +159,10 @@ export function setDMRoom(roomId, userId) { /** * Given a room, estimate which of its members is likely to * be the target if the room were a DM room and return that user. + * + * @param {Object} room Target room + * @param {string} myUserId User ID of the current user + * @returns {string} User ID of the user that the room is probably a DM with */ function guessDMRoomTargetId(room, myUserId) { let oldestTs; @@ -173,7 +177,7 @@ function guessDMRoomTargetId(room, myUserId) { oldestTs = user.events.member.getTs(); } } - if (oldestUser) return oldestUser; + if (oldestUser) return oldestUser.userId; // if there are no joined members other than us, use the oldest member for (const user of room.currentState.getMembers()) { @@ -186,5 +190,5 @@ function guessDMRoomTargetId(room, myUserId) { } if (oldestUser === undefined) return myUserId; - return oldestUser; + return oldestUser.userId; } diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 4fb5df214b..2c54344a30 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -252,9 +252,7 @@ export default React.createClass({ for (let i = 0; i < this.state.zxcvbnResult.feedback.suggestions.length; ++i) { suggestions.push(
{this.state.zxcvbnResult.feedback.suggestions[i]}
); } - const suggestionBlock = suggestions.length > 0 ?
- {suggestions} -
: null; + const suggestionBlock =
{suggestions.length > 0 ? suggestions : _t("Keep going...")}
; helpText =
{this.state.zxcvbnResult.feedback.warning} diff --git a/src/components/structures/CompatibilityPage.js b/src/components/structures/CompatibilityPage.js index 3c5005c053..28521cb1b7 100644 --- a/src/components/structures/CompatibilityPage.js +++ b/src/components/structures/CompatibilityPage.js @@ -41,10 +41,15 @@ module.exports = React.createClass({

{ _t("Sorry, your browser is not able to run Riot.", {}, { 'b': (sub) => {sub} }) }

- { _t("Riot uses many advanced browser features, some of which are not available or experimental in your current browser.") } + { _t( + "Riot uses many advanced browser features, some of which are not available " + + "or experimental in your current browser.", + ) }

- { _t('Please install Chrome or Firefox for the best experience.', + { _t( + 'Please install Chrome or Firefox ' + + 'for the best experience.', {}, { 'chromeLink': (sub) => {sub}, @@ -60,7 +65,12 @@ module.exports = React.createClass({ )}

- { _t("With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!") } + { _t( + "With your current browser, the look and feel of the application may be " + + "completely incorrect, and some or all features may not function. " + + "If you want to try it anyway you can continue, but you are on your own in terms " + + "of any issues you may encounter!", + ) }

{ this.state.errorString }
-
- + + ); }, }); diff --git a/src/components/structures/login/Registration.js b/src/components/structures/auth/Registration.js similarity index 92% rename from src/components/structures/login/Registration.js rename to src/components/structures/auth/Registration.js index fa5a02e881..fe6fb078e3 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -1,7 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd +Copyright 2018, 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,11 +24,10 @@ import PropTypes from 'prop-types'; import sdk from '../../../index'; import MatrixClientPeg from '../../../MatrixClientPeg'; -import RegistrationForm from '../../views/login/RegistrationForm'; +import RegistrationForm from '../../views/auth/RegistrationForm'; import RtsClient from '../../../RtsClient'; import { _t, _td } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; -import SettingsStore from "../../../settings/SettingsStore"; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; const MIN_PASSWORD_LENGTH = 6; @@ -397,14 +396,12 @@ module.exports = React.createClass({ }, render: function() { - const LoginHeader = sdk.getComponent('login.LoginHeader'); - const LoginFooter = sdk.getComponent('login.LoginFooter'); - const LoginPage = sdk.getComponent('login.LoginPage'); + const AuthHeader = sdk.getComponent('auth.AuthHeader'); + const AuthBody = sdk.getComponent("auth.AuthBody"); + const AuthPage = sdk.getComponent('auth.AuthPage'); const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); const Spinner = sdk.getComponent("elements.Spinner"); - const ServerConfig = sdk.getComponent('views.login.ServerConfig'); - - const theme = SettingsStore.getValue("theme"); + const ServerConfig = sdk.getComponent('views.auth.ServerConfig'); let registerBody; if (this.state.doingUIAuth) { @@ -458,47 +455,40 @@ module.exports = React.createClass({ ); } - let header; let errorText; - // FIXME: remove hardcoded Status team tweaks at some point const err = this.state.errorText || this.props.defaultServerDiscoveryError; - if (theme === 'status' && err) { - header =
{ err }
; - } else { - header =

{ _t('Create an account') }

; - if (err) { - errorText =
{ err }
; - } + const header =

{ _t('Create an account') }

; + if (err) { + errorText =
{ err }
; } let signIn; if (!this.state.doingUIAuth) { signIn = ( - { theme === 'status' ? _t('Sign in') : _t('I already have an account') } + { _t('I already have an account') } ); } - const LanguageSelector = sdk.getComponent('structures.login.LanguageSelector'); + const LanguageSelector = sdk.getComponent('structures.auth.LanguageSelector'); return ( - -
- + + + { header } { registerBody } { signIn } { errorText } - -
-
+ + ); }, }); diff --git a/src/components/views/auth/AuthBody.js b/src/components/views/auth/AuthBody.js new file mode 100644 index 0000000000..9a078efb52 --- /dev/null +++ b/src/components/views/auth/AuthBody.js @@ -0,0 +1,27 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +import React from 'react'; + +export default class AuthBody extends React.PureComponent { + render() { + return
+ { this.props.children } +
; + } +} diff --git a/src/components/structures/LoginBox.js b/src/components/views/auth/AuthButtons.js similarity index 67% rename from src/components/structures/LoginBox.js rename to src/components/views/auth/AuthButtons.js index 168014daa5..35bfabbbca 100644 --- a/src/components/structures/LoginBox.js +++ b/src/components/views/auth/AuthButtons.js @@ -1,6 +1,6 @@ /* Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd +Copyright 2018, 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,12 +18,12 @@ limitations under the License. 'use strict'; const React = require('react'); -import { _t } from '../../languageHandler'; -const dis = require('../../dispatcher'); -const AccessibleButton = require('../../components/views/elements/AccessibleButton'); +import { _t } from '../../../languageHandler'; +const dis = require('../../../dispatcher'); +const AccessibleButton = require('../elements/AccessibleButton'); module.exports = React.createClass({ - displayName: 'LoginBox', + displayName: 'AuthButtons', propTypes: { }, @@ -38,18 +38,18 @@ module.exports = React.createClass({ render: function() { const loginButton = ( -
- +
+ { _t("Login") } - + { _t("Register") }
); return ( -
+
{ loginButton }
); diff --git a/src/components/views/login/LoginFooter.js b/src/components/views/auth/AuthFooter.js similarity index 89% rename from src/components/views/login/LoginFooter.js rename to src/components/views/auth/AuthFooter.js index 392d36e288..ea43bf322c 100644 --- a/src/components/views/login/LoginFooter.js +++ b/src/components/views/auth/AuthFooter.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,11 +21,11 @@ import { _t } from '../../../languageHandler'; import React from 'react'; module.exports = React.createClass({ - displayName: 'LoginFooter', + displayName: 'AuthFooter', render: function() { return ( -
+
{ _t("powered by Matrix") }
); diff --git a/src/components/views/login/LoginHeader.js b/src/components/views/auth/AuthHeader.js similarity index 74% rename from src/components/views/login/LoginHeader.js rename to src/components/views/auth/AuthHeader.js index cd1f9c6a28..c1d831a70a 100644 --- a/src/components/views/login/LoginHeader.js +++ b/src/components/views/auth/AuthHeader.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,14 +18,17 @@ limitations under the License. 'use strict'; const React = require('react'); +import sdk from '../../../index'; module.exports = React.createClass({ - displayName: 'LoginHeader', + displayName: 'AuthHeader', render: function() { + const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo'); + return ( -
- Matrix +
+
); }, diff --git a/src/components/views/auth/AuthHeaderLogo.js b/src/components/views/auth/AuthHeaderLogo.js new file mode 100644 index 0000000000..9edf149a83 --- /dev/null +++ b/src/components/views/auth/AuthHeaderLogo.js @@ -0,0 +1,27 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +import React from 'react'; + +export default class AuthHeaderLogo extends React.PureComponent { + render() { + return
+ Matrix +
; + } +} diff --git a/src/components/views/auth/AuthPage.js b/src/components/views/auth/AuthPage.js new file mode 100644 index 0000000000..8cb8cf7d53 --- /dev/null +++ b/src/components/views/auth/AuthPage.js @@ -0,0 +1,38 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +const React = require('react'); +import sdk from '../../../index'; + +module.exports = React.createClass({ + displayName: 'AuthPage', + + render: function() { + const AuthFooter = sdk.getComponent('auth.AuthFooter'); + + return ( +
+
+ { this.props.children } +
+ +
+ ); + }, +}); diff --git a/src/components/views/login/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js similarity index 100% rename from src/components/views/login/CaptchaForm.js rename to src/components/views/auth/CaptchaForm.js diff --git a/src/components/views/login/CountryDropdown.js b/src/components/views/auth/CountryDropdown.js similarity index 98% rename from src/components/views/login/CountryDropdown.js rename to src/components/views/auth/CountryDropdown.js index 8c4467bb99..5f5dbf41a4 100644 --- a/src/components/views/login/CountryDropdown.js +++ b/src/components/views/auth/CountryDropdown.js @@ -70,7 +70,7 @@ export default class CountryDropdown extends React.Component { } _flagImgForIso2(iso2) { - return ; + return ; } _getShortOption(iso2) { diff --git a/src/components/views/login/CustomServerDialog.js b/src/components/views/auth/CustomServerDialog.js similarity index 100% rename from src/components/views/login/CustomServerDialog.js rename to src/components/views/auth/CustomServerDialog.js diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js similarity index 99% rename from src/components/views/login/InteractiveAuthEntryComponents.js rename to src/components/views/auth/InteractiveAuthEntryComponents.js index 73b46959b0..6a78898179 100644 --- a/src/components/views/login/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -187,7 +187,7 @@ export const RecaptchaAuthEntry = React.createClass({ return ; } - const CaptchaForm = sdk.getComponent("views.login.CaptchaForm"); + const CaptchaForm = sdk.getComponent("views.auth.CaptchaForm"); const sitePublicKey = this.props.stageParams.public_key; let errorSection; diff --git a/src/components/views/login/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js similarity index 99% rename from src/components/views/login/PasswordLogin.js rename to src/components/views/auth/PasswordLogin.js index 59d4db379c..1d36c52a24 100644 --- a/src/components/views/login/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -201,7 +201,7 @@ class PasswordLogin extends React.Component { disabled={disabled} />; case PasswordLogin.LOGIN_FIELD_PHONE: { - const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); + const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); classes.mx_Login_phoneNumberField = true; classes.mx_Login_field_has_prefix = true; classes.error = this.props.loginIncorrect && !this.state.phoneNumber; diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js similarity index 99% rename from src/components/views/login/RegistrationForm.js rename to src/components/views/auth/RegistrationForm.js index 137aeada91..a0fc79b947 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -323,7 +323,7 @@ module.exports = React.createClass({ } } - const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); + const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); let phoneSection; if (!SdkConfig.get().disable_3pid_login) { const phonePlaceholder = this._authStepIsRequired('m.login.msisdn') ? _t("Mobile phone number") : _t("Mobile phone number (optional)"); diff --git a/src/components/views/login/ServerConfig.js b/src/components/views/auth/ServerConfig.js similarity index 98% rename from src/components/views/login/ServerConfig.js rename to src/components/views/auth/ServerConfig.js index 2f04011273..57535e80d8 100644 --- a/src/components/views/login/ServerConfig.js +++ b/src/components/views/auth/ServerConfig.js @@ -138,7 +138,7 @@ module.exports = React.createClass({ }, showHelpPopup: function() { - const CustomServerDialog = sdk.getComponent('login.CustomServerDialog'); + const CustomServerDialog = sdk.getComponent('auth.CustomServerDialog'); Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog); }, diff --git a/src/components/views/context_menus/GroupInviteTileContextMenu.js b/src/components/views/context_menus/GroupInviteTileContextMenu.js index e30acca16d..4a8a33614b 100644 --- a/src/components/views/context_menus/GroupInviteTileContextMenu.js +++ b/src/components/views/context_menus/GroupInviteTileContextMenu.js @@ -79,7 +79,7 @@ export default class GroupInviteTileContextMenu extends React.Component { render() { return
- + { _t('Reject') }
; diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index 8e56c055f9..521282488e 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -245,26 +245,26 @@ module.exports = React.createClass({ return (
- +
- - + + { _t('All messages (noisy)') }
- - + + { _t('All messages') }
- - + + { _t('Mentions only') }
- - + + { _t('Mute') }
@@ -298,7 +298,7 @@ module.exports = React.createClass({ return (
- + { leaveText }
@@ -327,18 +327,18 @@ module.exports = React.createClass({ return (
- - + + { _t('Favourite') }
- - + + { _t('Low Priority') }
- - + + { _t('Direct Chat') }
diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js index 32f5365b82..8b868e7b11 100644 --- a/src/components/views/context_menus/TagTileContextMenu.js +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -59,7 +59,7 @@ export default class TagTileContextMenu extends React.Component {
@@ -67,7 +67,7 @@ export default class TagTileContextMenu extends React.Component {

- + { _t('Remove') }
; diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index 3e9052cc34..82ea649db4 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -111,7 +111,7 @@ export default React.createClass({ let cancelButton; if (this.props.hasCancel) { cancelButton = - + ; } diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js index 19b7d83717..3171c2db39 100644 --- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -127,7 +127,7 @@ export default class ChatCreateOrReuseDialog extends React.Component { onClick={this.props.onNewDMClick} >
- +
{ _t("Start new chat") }
; diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.js index f074d9b1fa..3a0b00adf2 100644 --- a/src/components/views/dialogs/ShareDialog.js +++ b/src/components/views/dialogs/ShareDialog.js @@ -26,11 +26,11 @@ import * as ContextualMenu from "../../structures/ContextualMenu"; const socials = [ { name: 'Facebook', - img: 'img/social/facebook.png', + img: require("../../../../res/img/social/facebook.png"), url: (url) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, }, { name: 'Twitter', - img: 'img/social/twitter-2.png', + img: require("../../../../res/img/social/twitter-2.png"), url: (url) => `https://twitter.com/home?status=${url}`, }, /* // icon missing name: 'Google Plus', @@ -38,15 +38,15 @@ const socials = [ url: (url) => `https://plus.google.com/share?url=${url}`, },*/ { name: 'LinkedIn', - img: 'img/social/linkedin.png', + img: require("../../../../res/img/social/linkedin.png"), url: (url) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`, }, { name: 'Reddit', - img: 'img/social/reddit.png', + img: require("../../../../res/img/social/reddit.png"), url: (url) => `http://www.reddit.com/submit?url=${url}`, }, { name: 'email', - img: 'img/social/email-1.png', + img: require("../../../../res/img/social/email-1.png"), url: (url) => `mailto:?body=${url}`, }, ]; @@ -202,7 +202,7 @@ export default class ShareDialog extends React.Component {
- +
{ diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js new file mode 100644 index 0000000000..dd404ce280 --- /dev/null +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -0,0 +1,99 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import {Tab, TabbedView} from "../../structures/TabbedView"; +import {_t, _td} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import GeneralSettingsTab from "../settings/tabs/GeneralSettingsTab"; +import dis from '../../../dispatcher'; + +// TODO: Ditch this whole component +export class TempTab extends React.Component { + static propTypes = { + onClose: PropTypes.func.isRequired, + }; + + componentDidMount(): void { + dis.dispatch({action: "view_old_user_settings"}); + this.props.onClose(); + } + + render() { + return
Hello World
; + } +} + +export default class UserSettingsDialog extends React.Component { + static propTypes = { + onFinished: PropTypes.func.isRequired, + }; + + _getTabs() { + return [ + new Tab( + _td("General"), + "mx_UserSettingsDialog_settingsIcon", + , + ), + new Tab( + _td("Notifications"), + "mx_UserSettingsDialog_bellIcon", +
Notifications Test
, + ), + new Tab( + _td("Preferences"), + "mx_UserSettingsDialog_preferencesIcon", +
Preferences Test
, + ), + new Tab( + _td("Voice & Video"), + "mx_UserSettingsDialog_voiceIcon", +
Voice Test
, + ), + new Tab( + _td("Security & Privacy"), + "mx_UserSettingsDialog_securityIcon", +
Security Test
, + ), + new Tab( + _td("Help & About"), + "mx_UserSettingsDialog_helpIcon", +
Help Test
, + ), + new Tab( + _td("Visit old settings"), + "mx_UserSettingsDialog_helpIcon", + , + ), + ]; + } + + render() { + return ( +
+
+ {_t("Settings")} + + + +
+ +
+ ); + } +} diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index fba9f0b714..fdb4f0a226 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2018, 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -184,6 +184,14 @@ export default React.createClass({ } else if (this.state.backupInfo === null) { title = _t("Error"); content = _t("No backup found!"); + } else if (this.state.recoverInfo && this.state.recoverInfo.imported === 0) { + title = _t("Error Restoring Backup"); + content =
+

{_t( + "Backup could not be decrypted with this key: " + + "please verify that you entered the correct recovery key.", + )}

+
; } else if (this.state.recoverInfo) { title = _t("Backup Restored"); let failedToDecrypt; diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js index ad51f501fb..053849863c 100644 --- a/src/components/views/directory/NetworkDropdown.js +++ b/src/components/views/directory/NetworkDropdown.js @@ -18,7 +18,7 @@ import React from 'react'; import MatrixClientPeg from '../../../MatrixClientPeg'; import {instanceForInstanceId} from '../../../utils/DirectoryUtils'; -const DEFAULT_ICON_URL = "img/network-matrix.svg"; +const DEFAULT_ICON_URL = require("../../../../res/img/network-matrix.svg"); export default class NetworkDropdown extends React.Component { constructor(props) { @@ -191,7 +191,7 @@ export default class NetworkDropdown extends React.Component { } else if (!instance) { key = server + '_all'; name = 'Matrix'; - icon = ; + icon = ; span_class = 'mx_NetworkDropdown_menu_network'; } else { key = server + '_inst_' + instance.instance_id; diff --git a/src/components/views/elements/AccessibleButton.js b/src/components/views/elements/AccessibleButton.js index e30ceb85fa..1c39ba4f49 100644 --- a/src/components/views/elements/AccessibleButton.js +++ b/src/components/views/elements/AccessibleButton.js @@ -28,41 +28,56 @@ import { KeyCode } from '../../../Keyboard'; * @returns {Object} rendered react */ export default function AccessibleButton(props) { - const {element, onClick, children, ...restProps} = props; - restProps.onClick = onClick; - // We need to consume enter onKeyDown and space onKeyUp - // otherwise we are risking also activating other keyboard focusable elements - // that might receive focus as a result of the AccessibleButtonClick action - // It's because we are using html buttons at a few places e.g. inside dialogs - // And divs which we report as role button to assistive technologies. - // Browsers handle space and enter keypresses differently and we are only adjusting to the - // inconsistencies here - restProps.onKeyDown = function(e) { - if (e.keyCode === KeyCode.ENTER) { - e.stopPropagation(); - e.preventDefault(); - return onClick(e); - } - if (e.keyCode === KeyCode.SPACE) { - e.stopPropagation(); - e.preventDefault(); - } - }; - restProps.onKeyUp = function(e) { - if (e.keyCode === KeyCode.SPACE) { - e.stopPropagation(); - e.preventDefault(); - return onClick(e); - } - if (e.keyCode === KeyCode.ENTER) { - e.stopPropagation(); - e.preventDefault(); - } - }; + const {element, onClick, children, kind, disabled, ...restProps} = props; + + if (!disabled) { + restProps.onClick = onClick; + // We need to consume enter onKeyDown and space onKeyUp + // otherwise we are risking also activating other keyboard focusable elements + // that might receive focus as a result of the AccessibleButtonClick action + // It's because we are using html buttons at a few places e.g. inside dialogs + // And divs which we report as role button to assistive technologies. + // Browsers handle space and enter keypresses differently and we are only adjusting to the + // inconsistencies here + restProps.onKeyDown = function(e) { + if (e.keyCode === KeyCode.ENTER) { + e.stopPropagation(); + e.preventDefault(); + return onClick(e); + } + if (e.keyCode === KeyCode.SPACE) { + e.stopPropagation(); + e.preventDefault(); + } + }; + restProps.onKeyUp = function(e) { + if (e.keyCode === KeyCode.SPACE) { + e.stopPropagation(); + e.preventDefault(); + return onClick(e); + } + if (e.keyCode === KeyCode.ENTER) { + e.stopPropagation(); + e.preventDefault(); + } + }; + } + restProps.tabIndex = restProps.tabIndex || "0"; restProps.role = "button"; restProps.className = (restProps.className ? restProps.className + " " : "") + "mx_AccessibleButton"; + + if (kind) { + // We apply a hasKind class to maintain backwards compatibility with + // buttons which might not know about kind and break + restProps.className += " mx_AccessibleButton_hasKind mx_AccessibleButton_kind_" + kind; + } + + if (disabled) { + restProps.className += " mx_AccessibleButton_disabled"; + } + return React.createElement(element, restProps, children); } @@ -76,6 +91,12 @@ AccessibleButton.propTypes = { children: PropTypes.node, element: PropTypes.string, onClick: PropTypes.func.isRequired, + + // The kind of button, similar to how Bootstrap works. + // See available classes for AccessibleButton for options. + kind: PropTypes.string, + + disabled: PropTypes.bool, }; AccessibleButton.defaultProps = { diff --git a/src/components/views/elements/AddressSelector.js b/src/components/views/elements/AddressSelector.js index b4279c7f70..23e6939a24 100644 --- a/src/components/views/elements/AddressSelector.js +++ b/src/components/views/elements/AddressSelector.js @@ -150,7 +150,7 @@ export default React.createClass({ showAddress={this.props.showAddress} justified={true} networkName="vector" - networkUrl="img/search-icon-vector.svg" + networkUrl={require("../../../../res/img/search-icon-vector.svg")} />
, ); diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js index 16e340756a..8011a6c55f 100644 --- a/src/components/views/elements/AddressTile.js +++ b/src/components/views/elements/AddressTile.js @@ -54,7 +54,7 @@ export default React.createClass({ address.avatarMxc, 25, 25, 'crop', )); } else if (address.addressType === 'email') { - imgUrls.push('img/icon-email-user.svg'); + imgUrls.push(require("../../../../res/img/icon-email-user.svg")); } // Removing networks for now as they're not really supported @@ -141,7 +141,7 @@ export default React.createClass({ if (this.props.canDismiss) { dismiss = (
- +
); } diff --git a/src/components/views/elements/AppPermission.js b/src/components/views/elements/AppPermission.js index 6b4536b620..8f4a434df0 100644 --- a/src/components/views/elements/AppPermission.js +++ b/src/components/views/elements/AppPermission.js @@ -47,7 +47,7 @@ export default class AppPermission extends React.Component { return (
- {_t('Warning!')} + {_t('Warning!')}
{ _t('Do you want to load widget from URL:') } { this.state.curlBase } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index f4f929a3c2..85d28baee7 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -582,19 +582,21 @@ export default class AppTile extends React.Component { // editing is done in scalar const showEditButton = Boolean(this._scalarClient && this._canUserModify()); const deleteWidgetLabel = this._deleteWidgetLabel(); - let deleteIcon = 'img/cancel_green.svg'; + let deleteIcon = require("../../../../res/img/cancel_green.svg"); let deleteClasses = 'mx_AppTileMenuBarWidget'; if (this._canUserModify()) { - deleteIcon = 'img/icon-delete-pink.svg'; + deleteIcon = require("../../../../res/img/icon-delete-pink.svg"); deleteClasses += ' mx_AppTileMenuBarWidgetDelete'; } // Picture snapshot - only show button when apps are maximised. const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show; - const showPictureSnapshotIcon = 'img/camera_green.svg'; - const popoutWidgetIcon = 'img/button-new-window.svg'; - const reloadWidgetIcon = 'img/button-refresh.svg'; - const windowStateIcon = (this.props.show ? 'img/minimize.svg' : 'img/maximize.svg'); + const showPictureSnapshotIcon = require("../../../../res/img/camera_green.svg"); + const popoutWidgetIcon = require("../../../../res/img/button-new-window.svg"); + const reloadWidgetIcon = require("../../../../res/img/button-refresh.svg"); + const minimizeIcon = require("../../../../res/img/minimize.svg"); + const maximizeIcon = require("../../../../res/img/maximize.svg"); + const windowStateIcon = (this.props.show ? minimizeIcon : maximizeIcon); let appTileClass; if (this.props.miniMode) { @@ -653,7 +655,7 @@ export default class AppTile extends React.Component { { /* Edit widget */ } { showEditButton && { return (
- +
{ props.errorMsg } diff --git a/src/components/views/elements/CreateRoomButton.js b/src/components/views/elements/CreateRoomButton.js index 177d033c75..da937be3e1 100644 --- a/src/components/views/elements/CreateRoomButton.js +++ b/src/components/views/elements/CreateRoomButton.js @@ -25,7 +25,7 @@ const CreateRoomButton = function(props) { diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 02fdc96a78..f4c016d9f2 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -62,13 +62,13 @@ const EditableItem = React.createClass({ { this.props.onAdd ?
{_t("Add")}
:
{_t("Delete")}
} diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js new file mode 100644 index 0000000000..69b890b911 --- /dev/null +++ b/src/components/views/elements/Field.js @@ -0,0 +1,54 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class Field extends React.PureComponent { + static propTypes = { + // The field's ID, which binds the input and label together. + id: PropTypes.string.isRequired, + // The field's type. Defaults to "text". + type: PropTypes.string, + // The field's label string. + label: PropTypes.string, + // The field's placeholder string. + placeholder: PropTypes.string, + // The type of field to create. Defaults to "input". Should be "input" or "select". + // To define options for a select, use + element: PropTypes.string, + // All other props pass through to the . + } + + render() { + const extraProps = Object.assign({}, this.props); + + // Remove explicit properties that shouldn't be copied + delete extraProps.element; + delete extraProps.children; + + // Set some defaults for the element + extraProps.type = extraProps.type || "text"; + + const element = this.props.element || "input"; + const fieldInput = React.createElement(element, extraProps, this.props.children); + + return
+ {fieldInput} + +
; + } +} diff --git a/src/components/views/elements/HomeButton.js b/src/components/views/elements/HomeButton.js index 05e21487eb..ecfce9b5f2 100644 --- a/src/components/views/elements/HomeButton.js +++ b/src/components/views/elements/HomeButton.js @@ -24,7 +24,7 @@ const HomeButton = function(props) { return ( diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 4ac5eda170..2c0f4a0d86 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -176,7 +176,7 @@ module.exports = React.createClass({
- { + {
diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index cce4705512..f82f309493 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -26,7 +26,7 @@ module.exports = React.createClass({ return (
- +
); }, diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js index f45053de44..40d4cf9377 100644 --- a/src/components/views/elements/ManageIntegsButton.js +++ b/src/components/views/elements/ManageIntegsButton.js @@ -80,7 +80,11 @@ export default class ManageIntegsButton extends React.Component { }); if (this.state.scalarError && !this.scalarClient.hasCredentials()) { - integrationsWarningTriangle = ; + integrationsWarningTriangle = ; // Popup shown when hovering over integrationsButton_error (via CSS) integrationsErrorPopup = ( @@ -91,7 +95,7 @@ export default class ManageIntegsButton extends React.Component { integrationsButton = ( - + { integrationsWarningTriangle } { integrationsErrorPopup } diff --git a/src/components/views/elements/MessageSpinner.js b/src/components/views/elements/MessageSpinner.js index 500c919d45..19d804f511 100644 --- a/src/components/views/elements/MessageSpinner.js +++ b/src/components/views/elements/MessageSpinner.js @@ -27,7 +27,7 @@ module.exports = React.createClass({ return (
{ msg }
  - +
); }, diff --git a/src/components/views/elements/RoomDirectoryButton.js b/src/components/views/elements/RoomDirectoryButton.js index d8f88034e3..1498157ee4 100644 --- a/src/components/views/elements/RoomDirectoryButton.js +++ b/src/components/views/elements/RoomDirectoryButton.js @@ -25,7 +25,7 @@ const RoomDirectoryButton = function(props) { diff --git a/src/components/views/elements/SettingsButton.js b/src/components/views/elements/SettingsButton.js index 215d757e6c..f63d17d93c 100644 --- a/src/components/views/elements/SettingsButton.js +++ b/src/components/views/elements/SettingsButton.js @@ -24,7 +24,7 @@ const SettingsButton = function(props) { return ( diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index 67fde4bf66..ca1ee0ef42 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -27,7 +27,7 @@ module.exports = React.createClass({ const imgClass = this.props.imgClassName || ""; return (
- +
); }, diff --git a/src/components/views/elements/StartChatButton.js b/src/components/views/elements/StartChatButton.js index 199c5e44a6..2132d63940 100644 --- a/src/components/views/elements/StartChatButton.js +++ b/src/components/views/elements/StartChatButton.js @@ -25,7 +25,7 @@ const StartChatButton = function(props) { diff --git a/src/components/views/globals/CookieBar.js b/src/components/views/globals/CookieBar.js index deb1cbffa8..f2bcd647d7 100644 --- a/src/components/views/globals/CookieBar.js +++ b/src/components/views/globals/CookieBar.js @@ -51,7 +51,7 @@ export default class CookieBar extends React.Component { const toolbarClasses = "mx_MatrixToolbar"; return (
- +
{ this.props.policyUrl ? _t( "Please help improve Riot.im by sending anonymous usage data. " + @@ -95,7 +95,7 @@ export default class CookieBar extends React.Component { { _t("Yes, I want to help!") } - {_t('Close')} + {_t('Close')}
); diff --git a/src/components/views/globals/MatrixToolbar.js b/src/components/views/globals/MatrixToolbar.js index 400d9e0c83..efcbfcba48 100644 --- a/src/components/views/globals/MatrixToolbar.js +++ b/src/components/views/globals/MatrixToolbar.js @@ -35,11 +35,11 @@ module.exports = React.createClass({ render: function() { return (
- +
{ _t('You are not receiving desktop notifications') } { _t('Enable them now') }
- {_t('Close')} + {_t('Close')}
); }, diff --git a/src/components/views/globals/NewVersionBar.js b/src/components/views/globals/NewVersionBar.js index 47eed4dc59..d802af63d4 100644 --- a/src/components/views/globals/NewVersionBar.js +++ b/src/components/views/globals/NewVersionBar.js @@ -96,7 +96,7 @@ export default React.createClass({ } return (
- +
{_t("A new version of Riot is available.")}
diff --git a/src/components/views/globals/PasswordNagBar.js b/src/components/views/globals/PasswordNagBar.js index 5e3da3ad6d..71901ad922 100644 --- a/src/components/views/globals/PasswordNagBar.js +++ b/src/components/views/globals/PasswordNagBar.js @@ -31,7 +31,7 @@ export default React.createClass({ return (
; + image = ; } else { - image = ; + image = ; } return ( @@ -83,7 +83,7 @@ export default React.createClass({ {message}
- {_t('Close')} + {_t('Close')}
); diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index ca59075912..aa40f1c8b3 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -188,7 +188,7 @@ module.exports = React.createClass({
- +
{ avatar } diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index 9a8196f12b..9045c92a2e 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -87,7 +87,7 @@ export default React.createClass({ const text = _t("and %(count)s others...", { count: overflowCount }); return ( + } name={text} presenceState="online" suppressOnHover={true} onClick={this._showFullMemberList} /> ); @@ -214,7 +214,7 @@ export default React.createClass({ onClick={this.onInviteToGroupButtonClick} >
- +
{ _t('Invite to this community') }
); diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js index 41e5f68736..05c6b9cfd4 100644 --- a/src/components/views/groups/GroupRoomInfo.js +++ b/src/components/views/groups/GroupRoomInfo.js @@ -215,7 +215,7 @@ module.exports = React.createClass({
- +
{ avatar } diff --git a/src/components/views/groups/GroupRoomList.js b/src/components/views/groups/GroupRoomList.js index 81fcfa8c8b..ec41cd036b 100644 --- a/src/components/views/groups/GroupRoomList.js +++ b/src/components/views/groups/GroupRoomList.js @@ -77,7 +77,7 @@ export default React.createClass({ const text = _t("and %(count)s others...", { count: overflowCount }); return ( + } name={text} presenceState="online" suppressOnHover={true} onClick={this._showFullRoomList} /> ); @@ -137,7 +137,7 @@ export default React.createClass({ onClick={this.onAddRoomToGroupButtonClick} >
- +
{ _t('Add rooms to this community') }
diff --git a/src/components/views/login/LoginPage.js b/src/components/views/login/LoginPage.js deleted file mode 100644 index 9eba53188e..0000000000 --- a/src/components/views/login/LoginPage.js +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -import SettingsStore from "../../../settings/SettingsStore"; - -const React = require('react'); - -module.exports = React.createClass({ - displayName: 'LoginPage', - - render: function() { - // FIXME: this should be turned into a proper skin with a StatusLoginPage component - if (SettingsStore.getValue("theme") === 'status') { - return ( -
-
- Status -
-
-
-

Status Community Chat

-
- A safer, decentralised communication - platform powered by Riot -
-
- { this.props.children } -
-

This channel is for our development community.

-

Interested in SNT and discussions on the cryptocurrency market?

-

Join Telegram Chat

-
-
-
- ); - } else { - return ( -
- { this.props.children } -
- ); - } - }, -}); diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index efc6c4a069..b4f26d0cbd 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -81,7 +81,7 @@ export default class MAudioBody extends React.Component { if (this.state.error !== null) { return ( - + { _t("Error decrypting audio") } ); @@ -94,7 +94,7 @@ export default class MAudioBody extends React.Component { // Not sure how tall the audio player is so not sure how tall it should actually be. return ( - {content.body} + {content.body} ); } diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 292ac25d42..781d340252 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -29,15 +29,15 @@ import request from 'browser-request'; import Modal from '../../../Modal'; -// A cached tinted copy of "img/download.svg" +// A cached tinted copy of require("../../../../res/img/download.svg") let tintedDownloadImageURL; // Track a list of mounted MFileBody instances so that we can update -// the "img/download.svg" when the tint changes. +// the require("../../../../res/img/download.svg") when the tint changes. let nextMountId = 0; const mounts = {}; /** - * Updates the tinted copy of "img/download.svg" when the tint changes. + * Updates the tinted copy of require("../../../../res/img/download.svg") when the tint changes. */ function updateTintedDownloadImage() { // Download the svg as an XML document. @@ -46,7 +46,7 @@ function updateTintedDownloadImage() { // Also note that we can't use fetch here because fetch doesn't support // file URLs, which the download image will be if we're running from // the filesystem (like in an Electron wrapper). - request({uri: "img/download.svg"}, (err, response, body) => { + request({uri: require("../../../../res/img/download.svg")}, (err, response, body) => { if (err) return; const svg = new DOMParser().parseFromString(body, "image/svg+xml"); @@ -254,7 +254,7 @@ module.exports = React.createClass({ }, tint: function() { - // Update our tinted copy of "img/download.svg" + // Update our tinted copy of require("../../../../res/img/download.svg") if (this.refs.downloadImage) { this.refs.downloadImage.src = tintedDownloadImageURL; } diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index dc891b86ff..dd1165cf58 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -282,7 +282,12 @@ export default class MImageBody extends React.Component { // e2e image hasn't been decrypted yet if (content.file !== undefined && this.state.decryptedUrl === null) { - placeholder = {content.body}; + placeholder = {content.body}; } else if (!this.state.imgLoaded) { // Deliberately, getSpinner is left unimplemented here, MStickerBody overides placeholder = this.getPlaceholder(); @@ -363,7 +368,7 @@ export default class MImageBody extends React.Component { if (this.state.error !== null) { return ( - + { _t("Error decrypting image") } ); diff --git a/src/components/views/messages/MStickerBody.js b/src/components/views/messages/MStickerBody.js index 82a530d503..55263ef7b7 100644 --- a/src/components/views/messages/MStickerBody.js +++ b/src/components/views/messages/MStickerBody.js @@ -35,7 +35,7 @@ export default class MStickerBody extends MImageBody { // img onLoad hasn't fired yet. getPlaceholder() { const TintableSVG = sdk.getComponent('elements.TintableSvg'); - return ; + return ; } // Tooltip to show on mouse over diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index 37fc94d1ed..f1199263e7 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -135,7 +135,7 @@ module.exports = React.createClass({ if (this.state.error !== null) { return ( - + { _t("Error decrypting video") } ); @@ -148,7 +148,7 @@ module.exports = React.createClass({ return (
- {content.body} + {content.body}
); diff --git a/src/components/views/messages/RoomCreate.js b/src/components/views/messages/RoomCreate.js index 0bb832f8ea..592afe984a 100644 --- a/src/components/views/messages/RoomCreate.js +++ b/src/components/views/messages/RoomCreate.js @@ -48,7 +48,7 @@ module.exports = React.createClass({ return
; // We should never have been instaniated in this case } return
- +
{_t("This room is a continuation of another conversation.")}
diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js index 6fcba1d815..6867b0bb9d 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.js +++ b/src/components/views/right_panel/GroupHeaderButtons.js @@ -65,12 +65,12 @@ export default class GroupHeaderButtons extends HeaderButtons { ]; return [ - , - , - , - - ./ + ./
); } diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index 5370b4d8b5..f52a3c52ab 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -147,7 +147,7 @@ module.exports = React.createClass({
- +
{ _t("Drop file here to upload") }
diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js index 3f3bdbf47a..009da0ebd4 100644 --- a/src/components/views/rooms/EntityTile.js +++ b/src/components/views/rooms/EntityTile.js @@ -160,7 +160,7 @@ const EntityTile = React.createClass({ if (this.props.showInviteButton) { inviteButton = (
- +
); } @@ -169,8 +169,8 @@ const EntityTile = React.createClass({ const powerStatus = this.props.powerStatus; if (powerStatus) { const src = { - [EntityTile.POWER_STATUS_MODERATOR]: "img/mod.svg", - [EntityTile.POWER_STATUS_ADMIN]: "img/admin.svg", + [EntityTile.POWER_STATUS_MODERATOR]: require("../../../../res/img/mod.svg"), + [EntityTile.POWER_STATUS_ADMIN]: require("../../../../res/img/admin.svg"), }[powerStatus]; const alt = { [EntityTile.POWER_STATUS_MODERATOR]: _t("Moderator"), diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 692111361a..acb122ad4e 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -768,23 +768,31 @@ module.exports.haveTileForEvent = function(e) { function E2ePadlockUndecryptable(props) { return ( ); } function E2ePadlockEncrypting(props) { - return ; + return ( + + ); } function E2ePadlockNotSent(props) { - return ; + return ( + + ); } function E2ePadlockVerified(props) { return ( ); } @@ -792,7 +800,7 @@ function E2ePadlockVerified(props) { function E2ePadlockUnverified(props) { return ( ); } @@ -800,7 +808,7 @@ function E2ePadlockUnverified(props) { function E2ePadlockUnencrypted(props) { return ( ); } diff --git a/src/components/views/rooms/JumpToBottomButton.js b/src/components/views/rooms/JumpToBottomButton.js new file mode 100644 index 0000000000..487071855f --- /dev/null +++ b/src/components/views/rooms/JumpToBottomButton.js @@ -0,0 +1,32 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { _t } from '../../../languageHandler'; +import AccessibleButton from '../elements/AccessibleButton'; + +export default (props) => { + let badge; + if (props.numUnreadMessages) { + badge = (
{props.numUnreadMessages}
); + } + return (
+ + + { badge } +
); +}; diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 7f74176878..1483fbffae 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -135,7 +135,7 @@ module.exports = React.createClass({
); diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index 41ed995ffb..b9c276f0d1 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -27,19 +27,19 @@ export default class MemberDeviceInfo extends React.Component { if (this.props.device.isBlocked()) { indicator = (
- {_t("Blacklisted")} + {_t("Blacklisted")}
); } else if (this.props.device.isVerified()) { indicator = (
- {_t("Verified")} + {_t("Verified")}
); } else { indicator = (
- {_t("Unverified")} + {_t("Unverified")}
); } diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 6a796bc160..9859861870 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -815,7 +815,7 @@ module.exports = withMatrixClient(React.createClass({ onClick={this.onNewDMClick} >
- +
{ _t("Start a chat") }
; @@ -963,7 +963,7 @@ module.exports = withMatrixClient(React.createClass({
- {_t('Close')} + {_t('Close')} { memberName }
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 0924a5ec38..f01ef3e3ec 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -253,7 +253,7 @@ module.exports = React.createClass({ const text = _t("and %(count)s others...", { count: overflowCount }); return ( + } name={text} presenceState="online" suppressOnHover={true} onClick={onClick} /> ); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 91ef549185..7681c2dc13 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -155,12 +155,12 @@ export default class MessageComposer extends React.Component { const fileAcceptedOrError = this.props.uploadAllowed(files[i]); if (fileAcceptedOrError === true) { acceptedFiles.push(
  • - { files[i].name || _t('Attachment') } + { files[i].name || _t('Attachment') }
  • ); fileList.push(files[i]); } else { failedFiles.push(
  • - { files[i].name || _t('Attachment') }

    { _t('Reason') + ": " + fileAcceptedOrError}

    + { files[i].name || _t('Attachment') }

    { _t('Reason') + ": " + fileAcceptedOrError}

  • ); } } @@ -320,11 +320,11 @@ export default class MessageComposer extends React.Component { const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); if (roomIsEncrypted) { // FIXME: show a /!\ if there are untrusted devices in the room... - e2eImg = 'img/e2e-verified.svg'; + e2eImg = require("../../../../res/img/e2e-verified.svg"); e2eTitle = _t('Encrypted room'); e2eClass = 'mx_MessageComposer_e2eIcon'; } else { - e2eImg = 'img/e2e-unencrypted.svg'; + e2eImg = require("../../../../res/img/e2e-unencrypted.svg"); e2eTitle = _t('Unencrypted room'); e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor'; } @@ -344,16 +344,16 @@ export default class MessageComposer extends React.Component { if (this.props.callState && this.props.callState !== 'ended') { hangupButton = - {_t('Hangup')} + {_t('Hangup')} ; } else { callButton = - + ; videoCallButton = - + ; } @@ -395,7 +395,7 @@ export default class MessageComposer extends React.Component { const uploadButton = ( - + @@ -451,7 +451,7 @@ export default class MessageComposer extends React.Component { controls.push(
    - + {_t("This room has been replaced and is no longer active.")}
    @@ -487,7 +487,7 @@ export default class MessageComposer extends React.Component { title={_t(name)} onMouseDown={onFormatButtonClicked} key={name} - src={`img/button-text-${name}${suffix}.svg`} + src={require(`../../../../res/img/button-text-${name}${suffix}.svg`)} height="17" />; }, ); @@ -500,11 +500,11 @@ export default class MessageComposer extends React.Component { + src={require(`../../../../res/img/button-md-${!this.state.inputState.isRichTextEnabled}.png`)} /> + src={require("../../../../res/img/icon-text-cancel.svg")} />
    ; } diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index ff7cfcbd6d..22bc965813 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -1599,7 +1599,7 @@ export default class MessageComposerInput extends React.Component { + src={require(`../../../../res/img/button-md-${!this.state.isRichTextEnabled}.png`)} /> - {_t('Unpin + {_t('Unpin
    ); } diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 50c40142da..3a0bc0e326 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -130,7 +130,7 @@ module.exports = React.createClass({
    - +

    { _t("Pinned Messages") }

    { tiles } diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index 46e2826634..7656d21832 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -68,7 +68,7 @@ export default class ReplyPreview extends React.Component { { '💬 ' + _t('Replying') }
    -
    diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 4292fa6a4d..3cdb9237be 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -312,14 +312,14 @@ module.exports = React.createClass({
    - {_t("Remove - + ; } @@ -353,7 +353,7 @@ module.exports = React.createClass({ { pinsIndicator } - + ; } @@ -361,7 +361,7 @@ module.exports = React.createClass({ // if (this.props.onLeaveClick) { // leave_button = //
    -// +// //
    ; // } @@ -369,7 +369,7 @@ module.exports = React.createClass({ if (this.props.onForgetClick) { forgetButton = - + ; } @@ -377,7 +377,7 @@ module.exports = React.createClass({ if (this.props.onSearchClick && this.props.inRoom) { searchButton = - + ; } @@ -385,7 +385,7 @@ module.exports = React.createClass({ if (this.props.inRoom) { shareRoomButton = - + ; } diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 1a714e3a84..01e6d280c6 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -593,7 +593,7 @@ module.exports = React.createClass({ subListsProps = subListsProps.filter((props => { const len = props.list.length + (props.extraTiles ? props.extraTiles.length : 0); - return len !== 0 || (props.onAddRoom && !this.props.searchFilter); + return len !== 0 || props.onAddRoom; })); return subListsProps.reduce((components, props, i) => { diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 3c767b726a..dbe409d6d7 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -124,7 +124,7 @@ module.exports = React.createClass({ emailMatchBlock =
    - /!\\ + /!\\
    { _t("This invitation was sent to an email address which is not associated with this account:") } diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 8cd559f2ea..9287eb3fae 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -616,7 +616,7 @@ module.exports = React.createClass({
    { settings } @@ -627,8 +627,8 @@ module.exports = React.createClass({
    diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 1a17fcf5ea..ed214812b5 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -392,7 +392,13 @@ module.exports = React.createClass({ let dmIndicator; if (this._isDirectMessageRoom(this.props.room.roomId)) { - dmIndicator = dm; + dmIndicator = dm; } return + } name={text} presenceState="online" suppressOnHover={true} onClick={this._showAll} /> ); diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js index 249ba8b93a..4ced9fb351 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.js @@ -26,7 +26,7 @@ export function CancelButton(props) { return ( - {_t("Cancel")} ); diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index c7d9f890a7..b8fe3afb97 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -152,7 +152,7 @@ export default class Stickerpicker extends React.Component { className='mx_Stickers_contentPlaceholder'>

    { _t("You don't currently have any stickerpacks enabled") }

    { _t("Add some now") }

    - +
    ); } @@ -351,7 +351,7 @@ export default class Stickerpicker extends React.Component { onClick={this._onHideStickersClick} ref='target' title={_t("Hide Stickers")}> - + ; } else { // Show show-stickers button @@ -362,7 +362,7 @@ export default class Stickerpicker extends React.Component { className="mx_MessageComposer_stickers" onClick={this._onShowStickersClick} title={_t("Show Stickers")}> - + ; } return
    diff --git a/src/components/views/rooms/TopUnreadMessagesBar.js b/src/components/views/rooms/TopUnreadMessagesBar.js index ed04ce594d..99f3b7fb23 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.js +++ b/src/components/views/rooms/TopUnreadMessagesBar.js @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd +Copyright 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index 9d49c35d83..dba40f033a 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -19,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; import WhoIsTyping from '../../../WhoIsTyping'; +import Timer from '../../../utils/Timer'; import MatrixClientPeg from '../../../MatrixClientPeg'; import MemberAvatar from '../avatars/MemberAvatar'; @@ -43,11 +44,18 @@ module.exports = React.createClass({ getInitialState: function() { return { usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), + // a map with userid => Timer to delay + // hiding the "x is typing" message for a + // user so hiding it can coincide + // with the sent message by the other side + // resulting in less timeline jumpiness + delayedStopTypingTimers: {}, }; }, componentWillMount: function() { MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping); + MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); }, componentDidUpdate: function(_, prevState) { @@ -64,18 +72,90 @@ module.exports = React.createClass({ const client = MatrixClientPeg.get(); if (client) { client.removeListener("RoomMember.typing", this.onRoomMemberTyping); + client.removeListener("Room.timeline", this.onRoomTimeline); + } + Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort()); + }, + + isVisible: function() { + return this.state.usersTyping.length !== 0 || Object.keys(this.state.delayedStopTypingTimers).length !== 0; + }, + + onRoomTimeline: function(event, room) { + if (room && room.roomId === this.props.room.roomId) { + const userId = event.getSender(); + // remove user from usersTyping + const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId); + this.setState({usersTyping}); + // abort timer if any + this._abortUserTimer(userId); } }, onRoomMemberTyping: function(ev, member) { + const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); this.setState({ - usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room), + delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), + usersTyping, }); }, - _renderTypingIndicatorAvatars: function(limit) { - let users = this.state.usersTyping; + _updateDelayedStopTypingTimers(usersTyping) { + const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { + return !usersTyping.some((b) => a.userId === b.userId); + }); + const usersThatStartedTyping = usersTyping.filter((a) => { + return !this.state.usersTyping.some((b) => a.userId === b.userId); + }); + // abort all the timers for the users that started typing again + usersThatStartedTyping.forEach((m) => { + const timer = this.state.delayedStopTypingTimers[m.userId]; + if (timer) { + timer.abort(); + } + }); + // prepare new delayedStopTypingTimers object to update state with + let delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); + // remove members that started typing again + delayedStopTypingTimers = usersThatStartedTyping.reduce((delayedStopTypingTimers, m) => { + delete delayedStopTypingTimers[m.userId]; + return delayedStopTypingTimers; + }, delayedStopTypingTimers); + // start timer for members that stopped typing + delayedStopTypingTimers = usersThatStoppedTyping.reduce((delayedStopTypingTimers, m) => { + if (!delayedStopTypingTimers[m.userId]) { + const timer = new Timer(5000); + delayedStopTypingTimers[m.userId] = timer; + timer.start(); + timer.finished().then( + () => this._removeUserTimer(m.userId), // on elapsed + () => {/* aborted */}, + ); + } + return delayedStopTypingTimers; + }, delayedStopTypingTimers); + return delayedStopTypingTimers; + }, + + _abortUserTimer: function(userId) { + const timer = this.state.delayedStopTypingTimers[userId]; + if (timer) { + timer.abort(); + this._removeUserTimer(userId); + } + }, + + _removeUserTimer: function(userId) { + const timer = this.state.delayedStopTypingTimers[userId]; + if (timer) { + const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); + delete delayedStopTypingTimers[userId]; + this.setState({delayedStopTypingTimers}); + } + }, + + _renderTypingIndicatorAvatars: function(users, limit) { let othersCount = 0; if (users.length > limit) { othersCount = users.length - limit + 1; @@ -106,8 +186,19 @@ module.exports = React.createClass({ }, render: function() { + let usersTyping = this.state.usersTyping; + const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers) + .map((userId) => this.props.room.getMember(userId)); + // append the users that have been reported not typing anymore + // but have a timeout timer running so they can disappear + // when a message comes in + usersTyping = usersTyping.concat(stoppedUsersOnTimer); + // sort them so the typing members don't change order when + // moved to delayedStopTypingTimers + usersTyping.sort((a, b) => a.name.localeCompare(b.name)); + const typingString = WhoIsTyping.whoIsTypingString( - this.state.usersTyping, + usersTyping, this.props.whoIsTypingLimit, ); if (!typingString) { @@ -119,7 +210,7 @@ module.exports = React.createClass({ return (
  • - { this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit) } + { this._renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) }
    { typingString } diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index 64822ace5f..27164e6517 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -141,7 +141,7 @@ export default withMatrixClient(React.createClass({ return
    ; } - const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); + const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); // XXX: This CSS relies on the CSS surrounding it in UserSettings as its in // a tabular format to align the submit buttons return ( @@ -166,7 +166,7 @@ export default withMatrixClient(React.createClass({
    - +
    ); diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 473b52ff12..8dc817faad 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -234,7 +234,7 @@ export default class KeyBackupPanel extends React.PureComponent { } let verifyButton; - if (!sig.device || !sig.device.isVerified()) { + if (sig.device && !sig.device.isVerified()) { verifyButton =

    { _t("Verify...") } diff --git a/src/components/views/settings/tabs/GeneralSettingsTab.js b/src/components/views/settings/tabs/GeneralSettingsTab.js new file mode 100644 index 0000000000..c8816325c0 --- /dev/null +++ b/src/components/views/settings/tabs/GeneralSettingsTab.js @@ -0,0 +1,95 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import {_t} from "../../../../languageHandler"; +import MatrixClientPeg from "../../../../MatrixClientPeg"; +import Field from "../../elements/Field"; +import AccessibleButton from "../../elements/AccessibleButton"; + +export default class GeneralSettingsTab extends React.Component { + constructor() { + super(); + + const client = MatrixClientPeg.get(); + this.state = { + userId: client.getUserId(), + displayName: client.getUser(client.getUserId()).displayName, + enableProfileSave: false, + }; + } + + _saveProfile = async (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (!this.state.enableProfileSave) return; + this.setState({enableProfileSave: false}); + + // TODO: What do we do about errors? + await MatrixClientPeg.get().setDisplayName(this.state.displayName); + + // TODO: Support avatars + + this.setState({enableProfileSave: true}); + }; + + _onDisplayNameChanged = (e) => { + this.setState({ + displayName: e.target.value, + enableProfileSave: true, + }); + }; + + _renderProfileSection() { + // TODO: Ditch avatar placeholder and use the real thing + const form = ( +
    +
    +
    +

    {this.state.userId}

    + +
    +
    +
    +
    +
    + + {_t("Save")} + + + ); + + return ( +
    + {_t("Profile")} + {form} +
    + ); + } + + render() { + return ( +
    +
    {_t("General")}
    + {this._renderProfileSection()} +
    + ); + } +} diff --git a/src/components/views/voip/IncomingCallBox.js b/src/components/views/voip/IncomingCallBox.js index 6cbaabe602..43c339d182 100644 --- a/src/components/views/voip/IncomingCallBox.js +++ b/src/components/views/voip/IncomingCallBox.js @@ -66,7 +66,7 @@ module.exports = React.createClass({ const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return (
    - +
    { incomingCallText }
    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0361c31bc6..9ab0d6b428 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -114,8 +114,6 @@ "Failed to invite": "Failed to invite", "Failed to invite users to the room:": "Failed to invite users to the room:", "Failed to invite the following users to the %(roomName)s room:": "Failed to invite the following users to the %(roomName)s room:", - "Waiting for %(userId)s to accept...": "Waiting for %(userId)s to accept...", - "Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...", "You need to be logged in.": "You need to be logged in.", "You need to be able to invite users to do that.": "You need to be able to invite users to do that.", "Unable to create widget.": "Unable to create widget.", @@ -264,6 +262,7 @@ "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", "Failed to join room": "Failed to join room", "Message Pinning": "Message Pinning", + "Tabbed settings": "Tabbed settings", "Custom user status messages": "Custom user status messages", "Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view", "Backup of encryption keys to server": "Backup of encryption keys to server", @@ -413,6 +412,10 @@ "Off": "Off", "On": "On", "Noisy": "Noisy", + "Display Name": "Display Name", + "Save": "Save", + "Profile": "Profile", + "General": "General", "Cannot add any more widgets": "Cannot add any more widgets", "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", @@ -545,7 +548,6 @@ "World readable": "World readable", "Guests can join": "Guests can join", "Failed to set avatar.": "Failed to set avatar.", - "Save": "Save", "(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", @@ -721,46 +723,6 @@ "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", "Message removed": "Message removed", - "Robot check is currently unavailable on desktop - please use a web browser": "Robot check is currently unavailable on desktop - please use a web browser", - "This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot", - "Custom Server Options": "Custom Server Options", - "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.", - "This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.", - "You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.", - "To continue, please enter your password.": "To continue, please enter your password.", - "Password:": "Password:", - "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", - "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", - "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", - "Please check your email to continue registration.": "Please check your email to continue registration.", - "Token incorrect": "Token incorrect", - "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", - "Please enter the code it contains:": "Please enter the code it contains:", - "Code": "Code", - "Start authentication": "Start authentication", - "powered by Matrix": "powered by Matrix", - "The email field must not be blank.": "The email field must not be blank.", - "The user name field must not be blank.": "The user name field must not be blank.", - "The phone number field must not be blank.": "The phone number field must not be blank.", - "The password field must not be blank.": "The password field must not be blank.", - "Username on %(hs)s": "Username on %(hs)s", - "User name": "User name", - "Mobile phone number": "Mobile phone number", - "Forgot your password?": "Forgot your password?", - "Matrix ID": "Matrix ID", - "%(serverName)s Matrix ID": "%(serverName)s Matrix ID", - "Sign in with": "Sign in with", - "Email address": "Email address", - "Sign in": "Sign in", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", - "Email address (optional)": "Email address (optional)", - "You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s", - "Mobile phone number (optional)": "Mobile phone number (optional)", - "Default server": "Default server", - "Custom server": "Custom server", - "Home server URL": "Home server URL", - "Identity server URL": "Identity server URL", - "What does this mean?": "What does this mean?", "Remove from community": "Remove from community", "Disinvite this user from community?": "Disinvite this user from community?", "Remove this user from community?": "Remove this user from community?", @@ -897,6 +859,7 @@ "And %(count)s more...|other": "And %(count)s more...", "ex. @bob:example.com": "ex. @bob:example.com", "Add User": "Add User", + "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", "That doesn't look like a valid email address": "That doesn't look like a valid email address", @@ -1032,6 +995,7 @@ "Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.", "Unable to add email address": "Unable to add email address", "Unable to verify email address.": "Unable to verify email address.", + "Email address": "Email address", "This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.", "Skip": "Skip", "Only use lower case letters, numbers and '=_-./'": "Only use lower case letters, numbers and '=_-./'", @@ -1063,9 +1027,16 @@ "Room contains unknown devices": "Room contains unknown devices", "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", "Unknown devices": "Unknown devices", + "Preferences": "Preferences", + "Voice & Video": "Voice & Video", + "Security & Privacy": "Security & Privacy", + "Help & About": "Help & About", + "Visit old settings": "Visit old settings", "Unable to load backup status": "Unable to load backup status", "Unable to restore backup": "Unable to restore backup", "No backup found!": "No backup found!", + "Error Restoring Backup": "Error Restoring Backup", + "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", "Backup Restored": "Backup Restored", "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", @@ -1112,6 +1083,44 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", + "Robot check is currently unavailable on desktop - please use a web browser": "Robot check is currently unavailable on desktop - please use a web browser", + "This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot", + "Custom Server Options": "Custom Server Options", + "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.", + "This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.", + "You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.", + "To continue, please enter your password.": "To continue, please enter your password.", + "Password:": "Password:", + "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", + "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", + "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", + "Please check your email to continue registration.": "Please check your email to continue registration.", + "Token incorrect": "Token incorrect", + "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", + "Please enter the code it contains:": "Please enter the code it contains:", + "Code": "Code", + "Start authentication": "Start authentication", + "powered by Matrix": "powered by Matrix", + "The email field must not be blank.": "The email field must not be blank.", + "The user name field must not be blank.": "The user name field must not be blank.", + "The phone number field must not be blank.": "The phone number field must not be blank.", + "The password field must not be blank.": "The password field must not be blank.", + "Username on %(hs)s": "Username on %(hs)s", + "User name": "User name", + "Mobile phone number": "Mobile phone number", + "Forgot your password?": "Forgot your password?", + "%(serverName)s Matrix ID": "%(serverName)s Matrix ID", + "Sign in with": "Sign in with", + "Sign in": "Sign in", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", + "Email address (optional)": "Email address (optional)", + "You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s", + "Mobile phone number (optional)": "Mobile phone number (optional)", + "Default server": "Default server", + "Custom server": "Custom server", + "Home server URL": "Home server URL", + "Identity server URL": "Identity server URL", + "What does this mean?": "What does this mean?", "Sorry, your browser is not able to run Riot.": "Sorry, your browser is not able to run Riot.", "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", "Please install Chrome or Firefox for the best experience.": "Please install Chrome or Firefox for the best experience.", @@ -1302,7 +1311,6 @@ "VoIP": "VoIP", "Email": "Email", "Add email address": "Add email address", - "Profile": "Profile", "Display name": "Display name", "Account": "Account", "To return to your account in future you need to set a password": "To return to your account in future you need to set a password", @@ -1346,7 +1354,6 @@ "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", "Sign in with single sign-on": "Sign in with single sign-on", "Try the app first": "Try the app first", - "Sign in to get started": "Sign in to get started", "Failed to fetch avatar URL": "Failed to fetch avatar URL", "Set a display name:": "Set a display name:", "Upload an avatar:": "Upload an avatar:", @@ -1399,6 +1406,7 @@ "File to import": "File to import", "Import": "Import", "Great! This passphrase looks strong enough.": "Great! This passphrase looks strong enough.", + "Keep going...": "Keep going...", "Secure your encrypted message history with a Recovery Passphrase.": "Secure your encrypted message history with a Recovery Passphrase.", "You'll need it if you log out or lose access to this device.": "You'll need it if you log out or lose access to this device.", "Enter a passphrase...": "Enter a passphrase...", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 4aba5a9d5f..c1ced35689 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -84,6 +84,12 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_tabbed_settings": { + isFeature: true, + displayName: _td("Tabbed settings"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_custom_status": { isFeature: true, displayName: _td("Custom user status messages"), diff --git a/src/stores/LifecycleStore.js b/src/stores/LifecycleStore.js index 2ce3be5a33..fcdfe93cf9 100644 --- a/src/stores/LifecycleStore.js +++ b/src/stores/LifecycleStore.js @@ -22,8 +22,8 @@ const INITIAL_STATE = { }; /** - * A class for storing application state to do with login/registration. This is a simple - * flux store that listens for actions and updates its state accordingly, informing any + * A class for storing application state to do with authentication. This is a simple flux + * store that listens for actions and updates its state accordingly, informing any * listeners (views) of state changes. */ class LifecycleStore extends Store { diff --git a/test/components/structures/login/Registration-test.js b/test/components/structures/auth/Registration-test.js similarity index 98% rename from test/components/structures/login/Registration-test.js rename to test/components/structures/auth/Registration-test.js index 7287bb0d95..4827bf4c12 100644 --- a/test/components/structures/login/Registration-test.js +++ b/test/components/structures/auth/Registration-test.js @@ -23,7 +23,7 @@ const expect = require('expect'); const testUtils = require('test-utils'); const sdk = require('matrix-react-sdk'); -const Registration = sdk.getComponent('structures.login.Registration'); +const Registration = sdk.getComponent('structures.auth.Registration'); let rtsClient; let client; diff --git a/test/components/views/login/RegistrationForm-test.js b/test/components/views/auth/RegistrationForm-test.js similarity index 97% rename from test/components/views/login/RegistrationForm-test.js rename to test/components/views/auth/RegistrationForm-test.js index 2d1c1be026..265f158bb9 100644 --- a/test/components/views/login/RegistrationForm-test.js +++ b/test/components/views/auth/RegistrationForm-test.js @@ -23,7 +23,7 @@ const expect = require('expect'); const testUtils = require('test-utils'); const sdk = require('matrix-react-sdk'); -const RegistrationForm = sdk.getComponent('views.login.RegistrationForm'); +const RegistrationForm = sdk.getComponent('views.auth.RegistrationForm'); const TEAM_CONFIG = { supportEmail: "support@some.domain",