diff --git a/README.md b/README.md index 73afe34df0..b3e96ef001 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Platform Targets: * WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox. * Mobile Web is not currently a target platform - instead please use the native iOS (https://github.com/matrix-org/matrix-ios-kit) and Android - (https://github.com/matrix-org/matrix-android-sdk) SDKs. + (https://github.com/matrix-org/matrix-android-sdk2) SDKs. All code lands on the `develop` branch - `master` is only used for stable releases. **Please file PRs against `develop`!!** diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss index 84f965e6bd..524f107165 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -101,7 +101,7 @@ limitations under the License. .mx_BaseAvatar { display: inline-flex; - margin: 5px 16px 5px 5px; + margin: auto 16px auto 5px; vertical-align: middle; } @@ -160,31 +160,32 @@ limitations under the License. } } - .mx_AddExistingToSpaceDialog_errorText { - font-weight: $font-semi-bold; - font-size: $font-12px; - line-height: $font-15px; - color: $notice-primary-color; - margin-bottom: 28px; - } - .mx_AddExistingToSpace { display: contents; } .mx_AddExistingToSpaceDialog_footer { display: flex; - margin-top: 32px; + margin-top: 20px; > span { flex-grow: 1; - font-size: $font-14px; + font-size: $font-12px; line-height: $font-15px; - font-weight: $font-semi-bold; + color: $secondary-fg-color; - .mx_AccessibleButton { - font-size: inherit; - display: inline-block; + .mx_ProgressBar { + height: 8px; + width: 100%; + + @mixin ProgressBarBorderRadius 8px; + } + + .mx_AddExistingToSpaceDialog_progressText { + margin-top: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-fg-color; } > * { @@ -192,8 +193,54 @@ limitations under the License. } } + .mx_AddExistingToSpaceDialog_error { + padding-left: 12px; + + > img { + align-self: center; + } + + .mx_AddExistingToSpaceDialog_errorHeading { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $notice-primary-color; + } + + .mx_AddExistingToSpaceDialog_errorCaption { + margin-top: 4px; + font-size: $font-12px; + line-height: $font-15px; + color: $primary-fg-color; + } + } + .mx_AccessibleButton { display: inline-block; + align-self: center; + } + + .mx_AccessibleButton_kind_primary { + padding: 8px 36px; + } + + .mx_AddExistingToSpaceDialog_retryButton { + margin-left: 12px; + padding-left: 24px; + position: relative; + + &::before { + content: ''; + position: absolute; + background-color: $primary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/retry.svg'); + width: 18px; + height: 18px; + left: 0; + } } .mx_AccessibleButton_kind_link { diff --git a/res/css/views/elements/_ProgressBar.scss b/res/css/views/elements/_ProgressBar.scss index 770978e921..c075ac74ff 100644 --- a/res/css/views/elements/_ProgressBar.scss +++ b/res/css/views/elements/_ProgressBar.scss @@ -21,7 +21,7 @@ progress.mx_ProgressBar { appearance: none; border: none; - @mixin ProgressBarBorderRadius "6px"; + @mixin ProgressBarBorderRadius 6px; @mixin ProgressBarColour $progressbar-fg-color; @mixin ProgressBarBgColour $progressbar-bg-color; ::-webkit-progress-value { diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 15daf81672..b87211a847 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -39,14 +39,14 @@ limitations under the License. width: 14px; // w&h are size of icon height: 18px; vertical-align: middle; - margin-right: 7px; // distance from left edge of waveform container (container has some margin too) + margin-right: 11px; // distance from left edge of waveform container (container has some margin too) background-color: $voice-record-icon-color; mask-repeat: no-repeat; mask-size: contain; mask-image: url('$(res)/img/element-icons/trashcan.svg'); } -.mx_VoiceMessagePrimaryContainer { +.mx_VoiceRecordComposerTile_recording.mx_VoiceMessagePrimaryContainer { // Note: remaining class properties are in the PlayerContainer CSS. margin: 6px; // force the composer area to put a gutter around us @@ -55,7 +55,9 @@ limitations under the License. position: relative; // important for the live circle &.mx_VoiceRecordComposerTile_recording { - padding-left: 16px; // +10px for the live circle, +6px for regular padding + // We are putting the circle in this padding, so we need +10px from the regular + // padding on the left side. + padding-left: 22px; &::before { animation: recording-pulse 2s infinite; @@ -65,8 +67,8 @@ limitations under the License. width: 10px; height: 10px; position: absolute; - left: 8px; - top: 16px; // vertically center + left: 12px; // 12px from the left edge for container padding + top: 18px; // vertically center (middle align with clock) border-radius: 10px; } } diff --git a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss index 109edfff81..0f879d209e 100644 --- a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss @@ -22,3 +22,34 @@ limitations under the License. .mx_HelpUserSettingsTab span.mx_AccessibleButton { word-break: break-word; } + +.mx_HelpUserSettingsTab code { + word-break: break-all; + user-select: all; +} + +.mx_HelpUserSettingsTab_accessToken { + display: flex; + justify-content: space-between; + border-radius: 5px; + border: solid 1px $light-fg-color; + margin-bottom: 10px; + margin-top: 10px; + padding: 10px; +} + +.mx_HelpUserSettingsTab_accessToken_copy { + flex-shrink: 0; + cursor: pointer; + margin-left: 20px; + display: inherit; +} + +.mx_HelpUserSettingsTab_accessToken_copy > div { + mask-image: url($copy-button-url); + background-color: $message-action-bar-fg-color; + margin-left: 5px; + width: 20px; + height: 20px; + background-repeat: no-repeat; +} diff --git a/res/css/views/voice_messages/_PlaybackContainer.scss b/res/css/views/voice_messages/_PlaybackContainer.scss index 49bd81ef81..64e8f445e1 100644 --- a/res/css/views/voice_messages/_PlaybackContainer.scss +++ b/res/css/views/voice_messages/_PlaybackContainer.scss @@ -19,8 +19,9 @@ limitations under the License. // Container for live recording and playback controls .mx_VoiceMessagePrimaryContainer { - padding: 6px; // makes us 4px taller than the send/stop button - padding-right: 5px; // there's 1px from the waveform itself, so account for that + // 7px top and bottom for visual design. 12px left & right, but the waveform (right) + // has a 1px padding on it that we want to account for. + padding: 7px 12px 7px 11px; background-color: $voice-record-waveform-bg-color; border-radius: 12px; @@ -30,11 +31,9 @@ limitations under the License. color: $voice-record-waveform-fg-color; font-size: $font-14px; + line-height: $font-24px; .mx_Waveform { - // We want the bars to be 2px shorter than the play/pause button in the waveform control - height: 28px; // default is 30px, so we're subtracting the 2px border off the bars - .mx_Waveform_bar { background-color: $voice-record-waveform-incomplete-fg-color; @@ -47,8 +46,8 @@ limitations under the License. } .mx_Clock { - padding-right: 4px; // isolate from waveform - padding-left: 8px; // isolate from live circle - width: 40px; // we're not using a monospace font, so fake it + width: 42px; // we're not using a monospace font, so fake it + padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended. + padding-left: 8px; // isolate from recording circle / play control } } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index d9fa61d055..25b59d3ff4 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -9,6 +9,7 @@ $header-panel-text-primary-color: #B9BEC6; $header-panel-text-secondary-color: #c8c8cd; $text-primary-color: #ffffff; $text-secondary-color: #B9BEC6; +$quaternary-fg-color: #6F7882; $search-bg-color: #181b21; $search-placeholder-color: #61708b; $room-highlight-color: #343a46; @@ -42,13 +43,13 @@ $preview-bar-bg-color: $header-panel-bg-color; $groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82); $inverted-bg-color: $base-color; -$voice-record-stop-border-color: #6F7882; // "Quarterly" +$voice-record-stop-border-color: $quaternary-fg-color; $voice-record-waveform-bg-color: #394049; // "Dark Tile" -$voice-record-waveform-fg-color: $tertiary-fg-color; -$voice-record-waveform-incomplete-fg-color: #5b646d; -$voice-record-icon-color: $tertiary-fg-color; +$voice-record-waveform-fg-color: $secondary-fg-color; +$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; +$voice-record-icon-color: $quaternary-fg-color; $voice-playback-button-bg-color: $tertiary-fg-color; -$voice-playback-button-fg-color: $bg-color; +$voice-playback-button-fg-color: #21262C; // "Separator" // used by AddressSelector $selected-color: $room-highlight-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 38bc1803a4..79b264ae1a 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -127,11 +127,11 @@ $groupFilterPanel-divider-color: $roomlist-header-color; // See non-legacy dark for variable information $voice-record-stop-border-color: #6F7882; $voice-record-waveform-bg-color: #394049; -$voice-record-waveform-fg-color: $tertiary-fg-color; -$voice-record-waveform-incomplete-fg-color: #5b646d; -$voice-record-icon-color: $tertiary-fg-color; +$voice-record-waveform-fg-color: $secondary-fg-color; +$voice-record-waveform-incomplete-fg-color: #6F7882; +$voice-record-icon-color: #6F7882; $voice-playback-button-bg-color: $tertiary-fg-color; -$voice-playback-button-fg-color: $bg-color; +$voice-playback-button-fg-color: #21262C; $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 3eefc6c0b8..9c47a06c15 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -192,15 +192,15 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%) $groupFilterPanel-divider-color: $roomlist-header-color; // See non-legacy _light for variable information -$voice-record-stop-border-color: #E3E8F0; $voice-record-stop-symbol-color: #ff4b55; -$voice-record-waveform-bg-color: #E3E8F0; -$voice-record-waveform-fg-color: $muted-fg-color; -$voice-record-waveform-incomplete-fg-color: #C1C6CD; $voice-record-live-circle-color: #ff4b55; -$voice-record-icon-color: $muted-fg-color; +$voice-record-stop-border-color: #E3E8F0; +$voice-record-waveform-bg-color: #E3E8F0; +$voice-record-waveform-fg-color: $secondary-fg-color; +$voice-record-waveform-incomplete-fg-color: #C1C6CD; +$voice-record-icon-color: $tertiary-fg-color; $voice-playback-button-bg-color: $primary-bg-color; -$voice-playback-button-fg-color: $muted-fg-color; +$voice-playback-button-fg-color: $secondary-fg-color; $fileinfo-bg-color: $voice-record-waveform-bg-color; $fileinfo-fg-color: $secondary-fg-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 036fed2d3a..750184a76b 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -21,6 +21,7 @@ $notice-primary-bg-color: rgba(255, 75, 85, 0.16); $primary-fg-color: #2e2f32; $secondary-fg-color: #737D8C; $tertiary-fg-color: #8D99A5; +$quaternary-fg-color: #C1C6CD; $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) @@ -182,15 +183,18 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%) $groupFilterPanel-divider-color: $roomlist-header-color; -$voice-record-stop-border-color: #E3E8F0; -$voice-record-stop-symbol-color: #ff4b55; // $warning-color, but without letting people change it in themes -$voice-record-waveform-bg-color: #E3E8F0; -$voice-record-waveform-fg-color: $muted-fg-color; -$voice-record-waveform-incomplete-fg-color: #C1C6CD; -$voice-record-live-circle-color: #ff4b55; // $warning-color, but without letting people change it in themes -$voice-record-icon-color: $muted-fg-color; +// These two don't change between themes. They are the $warning-color, but we don't +// want custom themes to affect them by accident. +$voice-record-stop-symbol-color: #ff4b55; +$voice-record-live-circle-color: #ff4b55; + +$voice-record-stop-border-color: #E3E8F0; // "Separator" +$voice-record-waveform-bg-color: #E3E8F0; // "Separator" +$voice-record-waveform-fg-color: $secondary-fg-color; +$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; +$voice-record-icon-color: $tertiary-fg-color; $voice-playback-button-bg-color: $primary-bg-color; -$voice-playback-button-fg-color: $muted-fg-color; +$voice-playback-button-fg-color: $secondary-fg-color; $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index cdf9dc02d3..a7a95d711c 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -52,7 +52,7 @@ import {useStateToggle} from "../../hooks/useStateToggle"; import SpaceStore from "../../stores/SpaceStore"; import FacePile from "../views/elements/FacePile"; import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog"; -import {allSettled} from "../../utils/promise"; +import {sleep} from "../../utils/promise"; import {calculateRoomVia} from "../../utils/permalinks/Permalinks"; interface IProps { @@ -389,15 +389,24 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => { let buttonLabel = _t("Skip for now"); if (selectedToAdd.size > 0) { onClick = async () => { - // TODO rate limiting setBusy(true); - try { - await allSettled(Array.from(selectedToAdd).map((room) => - SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room)))); - onFinished(true); - } catch (e) { - console.error("Failed to add rooms to space", e); - setError(_t("Failed to add rooms to space")); + + for (const room of selectedToAdd) { + const via = calculateRoomVia(room); + try { + await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => { + if (e.errcode === "M_LIMIT_EXCEEDED") { + await sleep(e.data.retry_after_ms); + return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry + } + + throw e; + }); + } catch (e) { + console.error("Failed to add rooms to space", e); + setError(_t("Failed to add rooms to space")); + break; + } } setBusy(false); }; diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 2253b525e0..a33248200c 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -29,12 +29,13 @@ import RoomAvatar from "../avatars/RoomAvatar"; import {getDisplayAliasForRoom} from "../../../Rooms"; import AccessibleButton from "../elements/AccessibleButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import {allSettled} from "../../../utils/promise"; +import {sleep} from "../../../utils/promise"; import DMRoomMap from "../../../utils/DMRoomMap"; import {calculateRoomVia} from "../../../utils/permalinks/Permalinks"; import StyledCheckbox from "../elements/StyledCheckbox"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; +import ProgressBar from "../elements/ProgressBar"; interface IProps extends IDialogProps { matrixClient: MatrixClient; @@ -46,7 +47,11 @@ const Entry = ({ room, checked, onChange }) => { return ; }; @@ -104,9 +109,9 @@ export const AddExistingToSpace: React.FC = ({ space, key={room.roomId} room={room} checked={selected.has(room)} - onChange={(checked) => { + onChange={onChange ? (checked) => { onChange(checked, room); - }} + } : null} />; }) } @@ -120,9 +125,9 @@ export const AddExistingToSpace: React.FC = ({ space, key={space.roomId} room={space} checked={selected.has(space)} - onChange={(checked) => { + onChange={onChange ? (checked) => { onChange(checked, space); - }} + } : null} />; }) } @@ -136,9 +141,9 @@ export const AddExistingToSpace: React.FC = ({ space, key={room.roomId} room={room} checked={selected.has(room)} - onChange={(checked) => { + onChange={onChange ? (checked) => { onChange(checked, room); - }} + } : null} />; }) } @@ -156,8 +161,8 @@ const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); const [selectedToAdd, setSelectedToAdd] = useState(new Set()); - const [busy, setBusy] = useState(false); - const [error, setError] = useState(""); + const [progress, setProgress] = useState(null); + const [error, setError] = useState(null); let spaceOptionSection; if (existingSubspaces.length > 0) { @@ -197,6 +202,82 @@ const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, ; + const addRooms = async () => { + setError(null); + setProgress(0); + + let error; + + for (const room of selectedToAdd) { + const via = calculateRoomVia(room); + try { + await SpaceStore.instance.addRoomToSpace(space, room.roomId, via).catch(async e => { + if (e.errcode === "M_LIMIT_EXCEEDED") { + await sleep(e.data.retry_after_ms); + return SpaceStore.instance.addRoomToSpace(space, room.roomId, via); // retry + } + + throw e; + }); + setProgress(i => i + 1); + } catch (e) { + console.error("Failed to add rooms to space", e); + setError(error = e); + break; + } + } + + if (!error) { + onFinished(true); + } + }; + + const busy = progress !== null; + + let footer; + if (error) { + footer = <> + + + +
{ _t("Not all selected were added") }
+
{ _t("Try again") }
+
+ + + { _t("Retry") } + + ; + } else if (busy) { + footer = + +
+ { _t("Adding rooms... (%(progress)s out of %(count)s)", { + count: selectedToAdd.size, + progress, + }) } +
+
; + } else { + footer = <> + +
{ _t("Want to add a new room instead?") }
+ onCreateRoomClick(cli, space)} kind="link"> + { _t("Create a new room") } + +
+ + + { _t("Add") } + + ; + } + return = ({ matrixClient: cli, space, onFinished={onFinished} fixedWidth={false} > - { error &&
{ error }
} - { + onChange={!busy && !error ? (checked, room) => { if (checked) { selectedToAdd.add(room); } else { selectedToAdd.delete(room); } setSelectedToAdd(new Set(selectedToAdd)); - }} + } : null} />
- -
{ _t("Don't want to add an existing room?") }
- onCreateRoomClick(cli, space)} kind="link"> - { _t("Create a new room") } - -
- - { - // TODO rate limiting - setBusy(true); - try { - await allSettled(Array.from(selectedToAdd).map((room) => - SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room)))); - onFinished(true); - } catch (e) { - console.error("Failed to add rooms to space", e); - setError(_t("Failed to add rooms to space")); - } - setBusy(false); - }} - > - { busy ? _t("Adding...") : _t("Add") } - + { footer }
; }; diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index fcacae2d39..05d487a9eb 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -114,6 +114,8 @@ export default class ImageView extends React.Component { componentWillUnmount() { this.focusLock.current.removeEventListener('wheel', this.onWheel); + window.removeEventListener("resize", this.calculateZoom); + this.image.current.removeEventListener("load", this.calculateZoom); } private calculateZoom = () => { diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index a009ead064..3fa0be478c 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -18,6 +18,7 @@ import React from 'react'; import {_t, getCurrentLanguage} from "../../../../../languageHandler"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import AccessibleButton from "../../../elements/AccessibleButton"; +import AccessibleTooltipButton from '../../../elements/AccessibleTooltipButton'; import SdkConfig from "../../../../../SdkConfig"; import createRoom from "../../../../../createRoom"; import Modal from "../../../../../Modal"; @@ -26,6 +27,9 @@ import PlatformPeg from "../../../../../PlatformPeg"; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import UpdateCheckButton from "../../UpdateCheckButton"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; +import { copyPlaintext } from "../../../../../utils/strings"; +import * as ContextMenu from "../../../../structures/ContextMenu"; +import { toRightOf } from "../../../../structures/ContextMenu"; interface IProps { closeSettingsFn: () => {}; @@ -38,6 +42,8 @@ interface IState { @replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab") export default class HelpUserSettingsTab extends React.Component { + protected closeCopiedTooltip: () => void; + constructor(props) { super(props); @@ -56,6 +62,12 @@ export default class HelpUserSettingsTab extends React.Component }); } + componentWillUnmount() { + // if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close + // the tooltip otherwise, such as pressing Escape + if (this.closeCopiedTooltip) this.closeCopiedTooltip(); + } + private onClearCacheAndReload = (e) => { if (!PlatformPeg.get()) return; @@ -153,6 +165,20 @@ export default class HelpUserSettingsTab extends React.Component ); } + onAccessTokenCopyClick = async (e) => { + e.preventDefault(); + const target = e.target; // copy target before we go async and React throws it away + + const successful = await copyPlaintext(MatrixClientPeg.get().getAccessToken()); + const buttonRect = target.getBoundingClientRect(); + const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); + const {close} = ContextMenu.createMenu(GenericTextContextMenu, { + ...toRightOf(buttonRect, 2), + message: successful ? _t('Copied!') : _t('Failed to copy'), + }); + this.closeCopiedTooltip = target.onmouseleave = close; + } + render() { const brand = SdkConfig.get().brand; @@ -269,12 +295,20 @@ export default class HelpUserSettingsTab extends React.Component
{_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}
{_t("Identity Server is")} {MatrixClientPeg.get().getIdentityServerUrl()}
- {_t("Access Token:") + ' '} - - <{ _t("click to reveal") }> - +
+
+ {_t("Access Token")}
+ {_t("Your access token gives full access to your account." + + " Do not share it with anyone." )} +
+ {MatrixClientPeg.get().getAccessToken()} + +
+

{_t("Clear cache and reload")} diff --git a/src/components/views/voice_messages/LiveRecordingWaveform.tsx b/src/components/views/voice_messages/LiveRecordingWaveform.tsx index e7c34c9177..aab89f6ab1 100644 --- a/src/components/views/voice_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/voice_messages/LiveRecordingWaveform.tsx @@ -15,12 +15,11 @@ limitations under the License. */ import React from "react"; -import {IRecordingUpdate, VoiceRecording} from "../../../voice/VoiceRecording"; +import {IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording} from "../../../voice/VoiceRecording"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import {arrayFastResample, arraySeed} from "../../../utils/arrays"; import {percentageOf} from "../../../utils/numbers"; import Waveform from "./Waveform"; -import {PLAYBACK_WAVEFORM_SAMPLES} from "../../../voice/Playback"; interface IProps { recorder: VoiceRecording; @@ -38,14 +37,14 @@ export default class LiveRecordingWaveform extends React.PureComponent { // The waveform and the downsample target are pretty close, so we should be fine to // do this, despite the docs on arrayFastResample. - const bars = arrayFastResample(Array.from(update.waveform), PLAYBACK_WAVEFORM_SAMPLES); + const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES); this.setState({ // The incoming data is between zero and one, but typically even screaming into a // microphone won't send you over 0.6, so we artificially adjust the gain for the diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2e5431620d..95c6feb973 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -833,7 +833,7 @@ "Match system theme": "Match system theme", "Use a system font": "Use a system font", "System font name": "System font name", - "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", + "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)": "Allow Peer-to-Peer for 1:1 calls (if you enable this, the other party might be able to see your IP address)", "Send analytics data": "Send analytics data", "Never send encrypted messages to unverified sessions from this session": "Never send encrypted messages to unverified sessions from this session", "Never send encrypted messages to unverified sessions in this room from this session": "Never send encrypted messages to unverified sessions in this room from this session", @@ -1252,8 +1252,9 @@ "olm version:": "olm version:", "Homeserver is": "Homeserver is", "Identity Server is": "Identity Server is", - "Access Token:": "Access Token:", - "click to reveal": "click to reveal", + "Access Token": "Access Token", + "Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.", + "Copy": "Copy", "Clear cache and reload": "Clear cache and reload", "Labs": "Labs", "Customise your experience with experimental labs features. Learn more.": "Customise your experience with experimental labs features. Learn more.", @@ -2033,10 +2034,11 @@ "Direct Messages": "Direct Messages", "Space selection": "Space selection", "Add existing rooms": "Add existing rooms", - "Don't want to add an existing room?": "Don't want to add an existing room?", + "Not all selected were added": "Not all selected were added", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...", + "Want to add a new room instead?": "Want to add a new room instead?", "Create a new room": "Create a new room", - "Failed to add rooms to space": "Failed to add rooms to space", - "Adding...": "Adding...", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", @@ -2347,7 +2349,6 @@ "Share Community": "Share Community", "Share Room Message": "Share Room Message", "Link to selected message": "Link to selected message", - "Copy": "Copy", "Command Help": "Command Help", "Failed to save space settings.": "Failed to save space settings.", "Space settings": "Space settings", @@ -2675,6 +2676,8 @@ "Failed to create initial space rooms": "Failed to create initial space rooms", "Skip for now": "Skip for now", "Creating rooms...": "Creating rooms...", + "Failed to add rooms to space": "Failed to add rooms to space", + "Adding...": "Adding...", "What do you want to organise?": "What do you want to organise?", "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.", "Share %(name)s": "Share %(name)s", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 2a26eeac13..1497a2208d 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -438,7 +438,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "webRtcAllowPeerToPeer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, - displayName: _td('Allow Peer-to-Peer for 1:1 calls'), + displayName: _td( + "Allow Peer-to-Peer for 1:1 calls " + + "(if you enable this, the other party might be able to see your IP address)", + ), default: true, invertedSettingName: 'webRtcForceTURN', }, diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 43822007c9..dcf9f891af 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -413,7 +413,19 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.spaceFilteredRooms.forEach((roomIds, s) => { // Update NotificationStates - this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => roomIds.has(room.roomId))); + this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { + if (roomIds.has(room.roomId)) { + // Don't aggregate notifications for DMs except in the Home Space + if (s !== HOME_SPACE) { + return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) + || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); + } + + return true; + } + + return false; + })); }); }, 100, {trailing: true, leading: true}); diff --git a/src/voice/Playback.ts b/src/voice/Playback.ts index ca48680ebd..caa5241e1a 100644 --- a/src/voice/Playback.ts +++ b/src/voice/Playback.ts @@ -29,7 +29,7 @@ export enum PlaybackState { Playing = "playing", // active progress through timeline } -export const PLAYBACK_WAVEFORM_SAMPLES = 35; +export const PLAYBACK_WAVEFORM_SAMPLES = 39; const DEFAULT_WAVEFORM = arraySeed(0, PLAYBACK_WAVEFORM_SAMPLES); export class Playback extends EventEmitter implements IDestroyable { diff --git a/src/voice/VoiceRecording.ts b/src/voice/VoiceRecording.ts index eb705200ca..c4a0a78ce5 100644 --- a/src/voice/VoiceRecording.ts +++ b/src/voice/VoiceRecording.ts @@ -33,6 +33,8 @@ const BITRATE = 24000; // 24kbps is pretty high quality for our use case in opus const TARGET_MAX_LENGTH = 120; // 2 minutes in seconds. Somewhat arbitrary, though longer == larger files. const TARGET_WARN_TIME_LEFT = 10; // 10 seconds, also somewhat arbitrary. +export const RECORDING_PLAYBACK_SAMPLES = 44; + export interface IRecordingUpdate { waveform: number[]; // floating points between 0 (low) and 1 (high). timeSeconds: number; // float