Merge branch 'develop' into reorganize-preferences
|
@ -15,7 +15,6 @@ module.exports = {
|
||||||
"prefer-promise-reject-errors": "off",
|
"prefer-promise-reject-errors": "off",
|
||||||
"no-async-promise-executor": "off",
|
"no-async-promise-executor": "off",
|
||||||
"quotes": "off",
|
"quotes": "off",
|
||||||
"indent": "off",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
overrides: [{
|
overrides: [{
|
||||||
|
|
222
CHANGELOG.md
|
@ -1,3 +1,225 @@
|
||||||
|
Changes in [3.21.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.21.0) (2021-05-17)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0-rc.1...v3.21.0)
|
||||||
|
|
||||||
|
## Security notice
|
||||||
|
|
||||||
|
matrix-react-sdk 3.21.0 fixes a low severity issue (GHSA-8796-gc9j-63rv)
|
||||||
|
related to file upload. When uploading a file, the local file preview can lead
|
||||||
|
to execution of scripts embedded in the uploaded file, but only after several
|
||||||
|
user interactions to open the preview in a separate tab. This only impacts the
|
||||||
|
local user while in the process of uploading. It cannot be exploited remotely
|
||||||
|
or by other users. Thanks to [Muhammad Zaid Ghifari](https://github.com/MR-ZHEEV)
|
||||||
|
for responsibly disclosing this via Matrix's Security Disclosure Policy.
|
||||||
|
|
||||||
|
## All changes
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 11.0.0
|
||||||
|
* [Release] Add missing space on beta feedback dialog
|
||||||
|
[\#6019](https://github.com/matrix-org/matrix-react-sdk/pull/6019)
|
||||||
|
* [Release] Add feedback mechanism for beta features, namely Spaces
|
||||||
|
[\#6013](https://github.com/matrix-org/matrix-react-sdk/pull/6013)
|
||||||
|
* Add feedback mechanism for beta features, namely Spaces
|
||||||
|
[\#6012](https://github.com/matrix-org/matrix-react-sdk/pull/6012)
|
||||||
|
|
||||||
|
Changes in [3.21.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.21.0-rc.1) (2021-05-11)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.20.0...v3.21.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 11.0.0-rc.1
|
||||||
|
* Add disclaimer about subspaces being experimental in add existing dialog
|
||||||
|
[\#5978](https://github.com/matrix-org/matrix-react-sdk/pull/5978)
|
||||||
|
* Spaces Beta release
|
||||||
|
[\#5933](https://github.com/matrix-org/matrix-react-sdk/pull/5933)
|
||||||
|
* Improve permissions error when adding new server to room directory
|
||||||
|
[\#6009](https://github.com/matrix-org/matrix-react-sdk/pull/6009)
|
||||||
|
* Allow user to progress through space creation & setup using Enter
|
||||||
|
[\#6006](https://github.com/matrix-org/matrix-react-sdk/pull/6006)
|
||||||
|
* Upgrade sanitize types
|
||||||
|
[\#6008](https://github.com/matrix-org/matrix-react-sdk/pull/6008)
|
||||||
|
* Upgrade `cheerio` and resolve type errors
|
||||||
|
[\#6007](https://github.com/matrix-org/matrix-react-sdk/pull/6007)
|
||||||
|
* Add slash commands support to edit message composer
|
||||||
|
[\#5865](https://github.com/matrix-org/matrix-react-sdk/pull/5865)
|
||||||
|
* Fix the two todays problem
|
||||||
|
[\#5940](https://github.com/matrix-org/matrix-react-sdk/pull/5940)
|
||||||
|
* Switch the Home Space out for an All rooms space
|
||||||
|
[\#5969](https://github.com/matrix-org/matrix-react-sdk/pull/5969)
|
||||||
|
* Show device ID in UserInfo when there is no device name
|
||||||
|
[\#5985](https://github.com/matrix-org/matrix-react-sdk/pull/5985)
|
||||||
|
* Switch back to release version of `sanitize-html`
|
||||||
|
[\#6005](https://github.com/matrix-org/matrix-react-sdk/pull/6005)
|
||||||
|
* Bump hosted-git-info from 2.8.8 to 2.8.9
|
||||||
|
[\#5998](https://github.com/matrix-org/matrix-react-sdk/pull/5998)
|
||||||
|
* Don't use the event's metadata to calc the scale of an image
|
||||||
|
[\#5982](https://github.com/matrix-org/matrix-react-sdk/pull/5982)
|
||||||
|
* Adjust MIME type of upload confirmation if needed
|
||||||
|
[\#5981](https://github.com/matrix-org/matrix-react-sdk/pull/5981)
|
||||||
|
* Forbid redaction of encryption events
|
||||||
|
[\#5991](https://github.com/matrix-org/matrix-react-sdk/pull/5991)
|
||||||
|
* Fix voice message playback being squished up against send button
|
||||||
|
[\#5988](https://github.com/matrix-org/matrix-react-sdk/pull/5988)
|
||||||
|
* Improve style of notification badges on the space panel
|
||||||
|
[\#5983](https://github.com/matrix-org/matrix-react-sdk/pull/5983)
|
||||||
|
* Add dev dependency for parse5 typings
|
||||||
|
[\#5990](https://github.com/matrix-org/matrix-react-sdk/pull/5990)
|
||||||
|
* Iterate Spaces admin UX around room management
|
||||||
|
[\#5977](https://github.com/matrix-org/matrix-react-sdk/pull/5977)
|
||||||
|
* Guard all isSpaceRoom calls behind the labs flag
|
||||||
|
[\#5979](https://github.com/matrix-org/matrix-react-sdk/pull/5979)
|
||||||
|
* Bump lodash from 4.17.20 to 4.17.21
|
||||||
|
[\#5986](https://github.com/matrix-org/matrix-react-sdk/pull/5986)
|
||||||
|
* Bump lodash from 4.17.19 to 4.17.21 in /test/end-to-end-tests
|
||||||
|
[\#5987](https://github.com/matrix-org/matrix-react-sdk/pull/5987)
|
||||||
|
* Bump ua-parser-js from 0.7.23 to 0.7.28
|
||||||
|
[\#5984](https://github.com/matrix-org/matrix-react-sdk/pull/5984)
|
||||||
|
* Update visual style of plain files in the timeline
|
||||||
|
[\#5971](https://github.com/matrix-org/matrix-react-sdk/pull/5971)
|
||||||
|
* Support for multiple streams (not MSC3077)
|
||||||
|
[\#5833](https://github.com/matrix-org/matrix-react-sdk/pull/5833)
|
||||||
|
* Update space ordering behaviour to match updates in MSC
|
||||||
|
[\#5963](https://github.com/matrix-org/matrix-react-sdk/pull/5963)
|
||||||
|
* Improve performance of search all spaces and space switching
|
||||||
|
[\#5976](https://github.com/matrix-org/matrix-react-sdk/pull/5976)
|
||||||
|
* Update colours and sizing for voice messages
|
||||||
|
[\#5970](https://github.com/matrix-org/matrix-react-sdk/pull/5970)
|
||||||
|
* Update link to Android SDK
|
||||||
|
[\#5973](https://github.com/matrix-org/matrix-react-sdk/pull/5973)
|
||||||
|
* Add cleanup functions for image view
|
||||||
|
[\#5962](https://github.com/matrix-org/matrix-react-sdk/pull/5962)
|
||||||
|
* Add a note about sharing your IP in P2P calls
|
||||||
|
[\#5961](https://github.com/matrix-org/matrix-react-sdk/pull/5961)
|
||||||
|
* Only aggregate DM notifications on the Space Panel in the Home Space
|
||||||
|
[\#5968](https://github.com/matrix-org/matrix-react-sdk/pull/5968)
|
||||||
|
* Add retry mechanism and progress bar to add existing to space dialog
|
||||||
|
[\#5975](https://github.com/matrix-org/matrix-react-sdk/pull/5975)
|
||||||
|
* Warn on access token reveal
|
||||||
|
[\#5755](https://github.com/matrix-org/matrix-react-sdk/pull/5755)
|
||||||
|
* Fix newly joined room appearing under the wrong space
|
||||||
|
[\#5945](https://github.com/matrix-org/matrix-react-sdk/pull/5945)
|
||||||
|
* Early rendering for voice messages in the timeline
|
||||||
|
[\#5955](https://github.com/matrix-org/matrix-react-sdk/pull/5955)
|
||||||
|
* Calculate the real waveform in the Playback class for voice messages
|
||||||
|
[\#5956](https://github.com/matrix-org/matrix-react-sdk/pull/5956)
|
||||||
|
* Don't recurse on arrayFastResample
|
||||||
|
[\#5957](https://github.com/matrix-org/matrix-react-sdk/pull/5957)
|
||||||
|
* Support a dark theme for voice messages
|
||||||
|
[\#5958](https://github.com/matrix-org/matrix-react-sdk/pull/5958)
|
||||||
|
* Handle no/blocked microphones in voice messages
|
||||||
|
[\#5959](https://github.com/matrix-org/matrix-react-sdk/pull/5959)
|
||||||
|
|
||||||
|
Changes in [3.20.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0) (2021-05-10)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.20.0-rc.1...v3.20.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 10.1.0
|
||||||
|
* [Release] Don't use the event's metadata to calc the scale of an image
|
||||||
|
[\#6004](https://github.com/matrix-org/matrix-react-sdk/pull/6004)
|
||||||
|
|
||||||
|
Changes in [3.20.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0-rc.1) (2021-05-04)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0...v3.20.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 10.1.0-rc.1
|
||||||
|
* Translations update from Weblate
|
||||||
|
[\#5966](https://github.com/matrix-org/matrix-react-sdk/pull/5966)
|
||||||
|
* Fix more space panel layout and hover behaviour issues
|
||||||
|
[\#5965](https://github.com/matrix-org/matrix-react-sdk/pull/5965)
|
||||||
|
* Fix edge case with space panel alignment with subspaces on ff
|
||||||
|
[\#5964](https://github.com/matrix-org/matrix-react-sdk/pull/5964)
|
||||||
|
* Fix saving room pill part to history
|
||||||
|
[\#5951](https://github.com/matrix-org/matrix-react-sdk/pull/5951)
|
||||||
|
* Generate room preview even when minimized
|
||||||
|
[\#5948](https://github.com/matrix-org/matrix-react-sdk/pull/5948)
|
||||||
|
* Another change from recovery passphrase to Security Phrase
|
||||||
|
[\#5934](https://github.com/matrix-org/matrix-react-sdk/pull/5934)
|
||||||
|
* Sort rooms in the add existing to space dialog based on recency
|
||||||
|
[\#5943](https://github.com/matrix-org/matrix-react-sdk/pull/5943)
|
||||||
|
* Inhibit sending RR when context switching to a room
|
||||||
|
[\#5944](https://github.com/matrix-org/matrix-react-sdk/pull/5944)
|
||||||
|
* Prevent room list keyboard handling from landing focus on hidden nodes
|
||||||
|
[\#5950](https://github.com/matrix-org/matrix-react-sdk/pull/5950)
|
||||||
|
* Make the text filter search all spaces instead of just the selected one
|
||||||
|
[\#5942](https://github.com/matrix-org/matrix-react-sdk/pull/5942)
|
||||||
|
* Enable indent rule and fix indent
|
||||||
|
[\#5931](https://github.com/matrix-org/matrix-react-sdk/pull/5931)
|
||||||
|
* Prevent peeking members from reacting
|
||||||
|
[\#5946](https://github.com/matrix-org/matrix-react-sdk/pull/5946)
|
||||||
|
* Disallow inline display maths
|
||||||
|
[\#5939](https://github.com/matrix-org/matrix-react-sdk/pull/5939)
|
||||||
|
* Space creation prompt user to add existing rooms for "Just Me" spaces
|
||||||
|
[\#5923](https://github.com/matrix-org/matrix-react-sdk/pull/5923)
|
||||||
|
* Add test coverage collection script
|
||||||
|
[\#5937](https://github.com/matrix-org/matrix-react-sdk/pull/5937)
|
||||||
|
* Fix joining room using via servers regression
|
||||||
|
[\#5936](https://github.com/matrix-org/matrix-react-sdk/pull/5936)
|
||||||
|
* Revert "Fixes the two Todays problem in Redaction"
|
||||||
|
[\#5938](https://github.com/matrix-org/matrix-react-sdk/pull/5938)
|
||||||
|
* Handle encoded matrix URLs
|
||||||
|
[\#5903](https://github.com/matrix-org/matrix-react-sdk/pull/5903)
|
||||||
|
* Render ignored users setting regardless of if there are any
|
||||||
|
[\#5860](https://github.com/matrix-org/matrix-react-sdk/pull/5860)
|
||||||
|
* Fix inserting trailing colon after mention/pill
|
||||||
|
[\#5830](https://github.com/matrix-org/matrix-react-sdk/pull/5830)
|
||||||
|
* Fixes the two Todays problem in Redaction
|
||||||
|
[\#5917](https://github.com/matrix-org/matrix-react-sdk/pull/5917)
|
||||||
|
* Fix page up/down scrolling only half a page
|
||||||
|
[\#5920](https://github.com/matrix-org/matrix-react-sdk/pull/5920)
|
||||||
|
* Voice messages: Composer controls
|
||||||
|
[\#5935](https://github.com/matrix-org/matrix-react-sdk/pull/5935)
|
||||||
|
* Support MSC3086 asserted identity
|
||||||
|
[\#5886](https://github.com/matrix-org/matrix-react-sdk/pull/5886)
|
||||||
|
* Handle possible edge case with getting stuck in "unsent messages" bar
|
||||||
|
[\#5930](https://github.com/matrix-org/matrix-react-sdk/pull/5930)
|
||||||
|
* Fix suggested rooms not showing up regression from room list optimisation
|
||||||
|
[\#5932](https://github.com/matrix-org/matrix-react-sdk/pull/5932)
|
||||||
|
* Broadcast language change to ElectronPlatform
|
||||||
|
[\#5913](https://github.com/matrix-org/matrix-react-sdk/pull/5913)
|
||||||
|
* Fix VoIP PIP frame color
|
||||||
|
[\#5701](https://github.com/matrix-org/matrix-react-sdk/pull/5701)
|
||||||
|
* Convert some Flow-typed files to TypeScript
|
||||||
|
[\#5912](https://github.com/matrix-org/matrix-react-sdk/pull/5912)
|
||||||
|
* Initial SpaceStore tests work
|
||||||
|
[\#5906](https://github.com/matrix-org/matrix-react-sdk/pull/5906)
|
||||||
|
* Fix issues with space hierarchy in layout and with incompatible servers
|
||||||
|
[\#5926](https://github.com/matrix-org/matrix-react-sdk/pull/5926)
|
||||||
|
* Scale all mxc thumbs using device pixel ratio for hidpi
|
||||||
|
[\#5928](https://github.com/matrix-org/matrix-react-sdk/pull/5928)
|
||||||
|
* Fix add existing to space dialog no longer showing rooms for public spaces
|
||||||
|
[\#5918](https://github.com/matrix-org/matrix-react-sdk/pull/5918)
|
||||||
|
* Disable spaces context switching for when exploring a space
|
||||||
|
[\#5924](https://github.com/matrix-org/matrix-react-sdk/pull/5924)
|
||||||
|
* Autofocus search box in the add existing to space dialog
|
||||||
|
[\#5921](https://github.com/matrix-org/matrix-react-sdk/pull/5921)
|
||||||
|
* Use label element in add existing to space dialog for easier hit target
|
||||||
|
[\#5922](https://github.com/matrix-org/matrix-react-sdk/pull/5922)
|
||||||
|
* Dynamic max and min zoom in the new ImageView
|
||||||
|
[\#5916](https://github.com/matrix-org/matrix-react-sdk/pull/5916)
|
||||||
|
* Improve message error states
|
||||||
|
[\#5897](https://github.com/matrix-org/matrix-react-sdk/pull/5897)
|
||||||
|
* Check for null room in `VisibilityProvider`
|
||||||
|
[\#5914](https://github.com/matrix-org/matrix-react-sdk/pull/5914)
|
||||||
|
* Add unit tests for various collection-based utility functions
|
||||||
|
[\#5910](https://github.com/matrix-org/matrix-react-sdk/pull/5910)
|
||||||
|
* Spaces visual fixes
|
||||||
|
[\#5909](https://github.com/matrix-org/matrix-react-sdk/pull/5909)
|
||||||
|
* Remove reliance on DOM API to generated message preview
|
||||||
|
[\#5908](https://github.com/matrix-org/matrix-react-sdk/pull/5908)
|
||||||
|
* Expand upon voice message event & include overall waveform
|
||||||
|
[\#5888](https://github.com/matrix-org/matrix-react-sdk/pull/5888)
|
||||||
|
* Use floats for image background opacity
|
||||||
|
[\#5905](https://github.com/matrix-org/matrix-react-sdk/pull/5905)
|
||||||
|
* Show invites to spaces at the top of the space panel
|
||||||
|
[\#5902](https://github.com/matrix-org/matrix-react-sdk/pull/5902)
|
||||||
|
* Improve edge cases with spaces context switching
|
||||||
|
[\#5899](https://github.com/matrix-org/matrix-react-sdk/pull/5899)
|
||||||
|
* Fix spaces notification dots wrongly including upgraded (hidden) rooms
|
||||||
|
[\#5900](https://github.com/matrix-org/matrix-react-sdk/pull/5900)
|
||||||
|
* Iterate the spaces face pile design
|
||||||
|
[\#5898](https://github.com/matrix-org/matrix-react-sdk/pull/5898)
|
||||||
|
* Fix alignment issue with nested spaces being cut off wrong
|
||||||
|
[\#5890](https://github.com/matrix-org/matrix-react-sdk/pull/5890)
|
||||||
|
|
||||||
Changes in [3.19.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0) (2021-04-26)
|
Changes in [3.19.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0) (2021-04-26)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0-rc.1...v3.19.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0-rc.1...v3.19.0)
|
||||||
|
|
|
@ -28,7 +28,7 @@ Platform Targets:
|
||||||
* WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox.
|
* 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
|
* Mobile Web is not currently a target platform - instead please use the native
|
||||||
iOS (https://github.com/matrix-org/matrix-ios-kit) and Android
|
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.
|
All code lands on the `develop` branch - `master` is only used for stable releases.
|
||||||
**Please file PRs against `develop`!!**
|
**Please file PRs against `develop`!!**
|
||||||
|
|
2
__mocks__/empty.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Yes, this is empty.
|
||||||
|
module.exports = {};
|
16
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.19.0",
|
"version": "3.21.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
"blueimp-canvas-to-blob": "^3.28.0",
|
"blueimp-canvas-to-blob": "^3.28.0",
|
||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.9",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"commonmark": "^0.29.3",
|
"commonmark": "^0.29.3",
|
||||||
"counterpart": "^0.18.6",
|
"counterpart": "^0.18.6",
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^0.1.0-beta.13",
|
"matrix-widget-api": "^0.1.0-beta.14",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"opus-recorder": "^8.0.3",
|
"opus-recorder": "^8.0.3",
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"rfc4648": "^1.4.0",
|
"rfc4648": "^1.4.0",
|
||||||
"sanitize-html": "github:apostrophecms/sanitize-html#3c7f93f2058f696f5359e3e58d464161647226db",
|
"sanitize-html": "^2.3.2",
|
||||||
"tar-js": "^0.3.0",
|
"tar-js": "^0.3.0",
|
||||||
"text-encoding-utf-8": "^1.0.2",
|
"text-encoding-utf-8": "^1.0.2",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
|
@ -132,11 +132,12 @@
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/node": "^14.14.22",
|
"@types/node": "^14.14.22",
|
||||||
"@types/pako": "^1.0.1",
|
"@types/pako": "^1.0.1",
|
||||||
|
"@types/parse5": "^6.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
"@types/react": "^16.9",
|
"@types/react": "^16.9",
|
||||||
"@types/react-dom": "^16.9.10",
|
"@types/react-dom": "^16.9.10",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "^1.27.0",
|
"@types/sanitize-html": "^2.3.1",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@types/zxcvbn": "^4.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||||
"@typescript-eslint/parser": "^4.14.0",
|
"@typescript-eslint/parser": "^4.14.0",
|
||||||
|
@ -185,7 +186,10 @@
|
||||||
],
|
],
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"\\.(gif|png|svg|ttf|woff2)$": "<rootDir>/__mocks__/imageMock.js",
|
"\\.(gif|png|svg|ttf|woff2)$": "<rootDir>/__mocks__/imageMock.js",
|
||||||
"\\$webapp/i18n/languages.json": "<rootDir>/__mocks__/languages.json"
|
"\\$webapp/i18n/languages.json": "<rootDir>/__mocks__/languages.json",
|
||||||
|
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
||||||
|
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
|
||||||
|
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js"
|
||||||
},
|
},
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"/node_modules/(?!matrix-js-sdk).+$"
|
"/node_modules/(?!matrix-js-sdk).+$"
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
||||||
@import "./views/avatars/_PulsedAvatar.scss";
|
@import "./views/avatars/_PulsedAvatar.scss";
|
||||||
@import "./views/avatars/_WidgetAvatar.scss";
|
@import "./views/avatars/_WidgetAvatar.scss";
|
||||||
|
@import "./views/beta/_BetaCard.scss";
|
||||||
@import "./views/context_menus/_CallContextMenu.scss";
|
@import "./views/context_menus/_CallContextMenu.scss";
|
||||||
@import "./views/context_menus/_IconizedContextMenu.scss";
|
@import "./views/context_menus/_IconizedContextMenu.scss";
|
||||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
|
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
|
||||||
@import "./views/dialogs/_AddressPickerDialog.scss";
|
@import "./views/dialogs/_AddressPickerDialog.scss";
|
||||||
@import "./views/dialogs/_Analytics.scss";
|
@import "./views/dialogs/_Analytics.scss";
|
||||||
|
@import "./views/dialogs/_BetaFeedbackDialog.scss";
|
||||||
@import "./views/dialogs/_BugReportDialog.scss";
|
@import "./views/dialogs/_BugReportDialog.scss";
|
||||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||||
|
@ -96,6 +98,7 @@
|
||||||
@import "./views/dialogs/_SpaceSettingsDialog.scss";
|
@import "./views/dialogs/_SpaceSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
|
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
|
||||||
@import "./views/dialogs/_TermsDialog.scss";
|
@import "./views/dialogs/_TermsDialog.scss";
|
||||||
|
@import "./views/dialogs/_UntrustedDeviceDialog.scss";
|
||||||
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
||||||
@import "./views/dialogs/_UserSettingsDialog.scss";
|
@import "./views/dialogs/_UserSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_WidgetCapabilitiesPromptDialog.scss";
|
@import "./views/dialogs/_WidgetCapabilitiesPromptDialog.scss";
|
||||||
|
@ -161,6 +164,7 @@
|
||||||
@import "./views/messages/_MStickerBody.scss";
|
@import "./views/messages/_MStickerBody.scss";
|
||||||
@import "./views/messages/_MTextBody.scss";
|
@import "./views/messages/_MTextBody.scss";
|
||||||
@import "./views/messages/_MVideoBody.scss";
|
@import "./views/messages/_MVideoBody.scss";
|
||||||
|
@import "./views/messages/_MVoiceMessageBody.scss";
|
||||||
@import "./views/messages/_MessageActionBar.scss";
|
@import "./views/messages/_MessageActionBar.scss";
|
||||||
@import "./views/messages/_MessageTimestamp.scss";
|
@import "./views/messages/_MessageTimestamp.scss";
|
||||||
@import "./views/messages/_MjolnirBody.scss";
|
@import "./views/messages/_MjolnirBody.scss";
|
||||||
|
@ -236,6 +240,7 @@
|
||||||
@import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss";
|
||||||
|
@import "./views/settings/tabs/user/_LabsUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
|
||||||
|
|
|
@ -115,8 +115,3 @@ limitations under the License.
|
||||||
border-top: 8px solid $menu-bg-color;
|
border-top: 8px solid $menu-bg-color;
|
||||||
border-right: 8px solid transparent;
|
border-right: 8px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ContextualMenu_spinner {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
|
@ -56,6 +56,12 @@ limitations under the License.
|
||||||
.mx_GroupFilterPanel .mx_TagTile {
|
.mx_GroupFilterPanel .mx_TagTile {
|
||||||
// opacity: 0.5;
|
// opacity: 0.5;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.mx_BetaDot {
|
||||||
|
position: absolute;
|
||||||
|
right: -13px;
|
||||||
|
top: -11px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupFilterPanel .mx_TagTile.mx_TagTile_prototype {
|
.mx_GroupFilterPanel .mx_TagTile.mx_TagTile_prototype {
|
||||||
|
|
|
@ -17,6 +17,11 @@ limitations under the License.
|
||||||
.mx_MyGroups {
|
.mx_MyGroups {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
.mx_BetaCard {
|
||||||
|
margin: 0 72px;
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MyGroups .mx_RoomHeader_simpleHeader {
|
.mx_MyGroups .mx_RoomHeader_simpleHeader {
|
||||||
|
@ -30,7 +35,7 @@ limitations under the License.
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MyGroups > :not(.mx_RoomHeader) {
|
.mx_MyGroups > :not(.mx_RoomHeader):not(.mx_BetaCard) {
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
margin: 40px;
|
margin: 40px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,10 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
.mx_SpaceItem {
|
.mx_SpaceItem {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
|
|
||||||
|
&.mx_SpaceItem_narrow {
|
||||||
|
align-self: baseline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceItem.collapsed {
|
.mx_SpaceItem.collapsed {
|
||||||
|
@ -233,7 +237,6 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 16px;
|
|
||||||
|
|
||||||
// Create a flexbox to make aligning dot badges easier
|
// Create a flexbox to make aligning dot badges easier
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -245,23 +248,37 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_NotificationBadge_dot {
|
.mx_NotificationBadge_dot {
|
||||||
// make the smaller dot occupy the same width for centering
|
// make the smaller dot occupy the same width for centering
|
||||||
margin-left: 7px;
|
margin: 0 7px;
|
||||||
margin-right: 7px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
.mx_SpaceButton {
|
.mx_SpaceButton {
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
right: -3px;
|
right: 0;
|
||||||
top: -3px;
|
top: 0;
|
||||||
|
|
||||||
|
.mx_NotificationBadge {
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NotificationBadge_dot {
|
||||||
|
margin: 0 -1px 0 0;
|
||||||
|
border: 3px solid $groupFilterPanel-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NotificationBadge_2char,
|
||||||
|
.mx_NotificationBadge_3char {
|
||||||
|
margin: -5px -5px 0 0;
|
||||||
|
border: 2px solid $groupFilterPanel-bg-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
|
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
|
||||||
// when we draw the selection border we move the relative bounds of our parent
|
// when we draw the selection border we move the relative bounds of our parent
|
||||||
// so update our position within the bounds of the parent to maintain position overall
|
// so update our position within the bounds of the parent to maintain position overall
|
||||||
right: -6px;
|
right: -3px;
|
||||||
top: -6px;
|
top: -3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,7 +292,7 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
.mx_SpaceButton:hover,
|
.mx_SpaceButton:hover,
|
||||||
.mx_SpaceButton:focus-within,
|
.mx_SpaceButton:focus-within,
|
||||||
.mx_SpaceButton_hasMenuOpen {
|
.mx_SpaceButton_hasMenuOpen {
|
||||||
&:not(.mx_SpaceButton_home) {
|
&:not(.mx_SpaceButton_home):not(.mx_SpaceButton_invite) {
|
||||||
// Hide the badge container on hover because it'll be a menu button
|
// Hide the badge container on hover because it'll be a menu button
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
|
@ -86,7 +86,7 @@ limitations under the License.
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
padding: 2px 8px;
|
padding: 4px 12px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
||||||
& + .mx_AccessibleButton {
|
& + .mx_AccessibleButton {
|
||||||
|
@ -94,6 +94,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger_outline,
|
||||||
|
.mx_AccessibleButton_kind_primary_outline {
|
||||||
|
padding: 3px 12px; // to account for the 1px border
|
||||||
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
@ -246,11 +251,17 @@ limitations under the License.
|
||||||
grid-row: 1/3;
|
grid-row: 1/3;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
padding: 8px 18px;
|
line-height: $font-24px;
|
||||||
|
padding: 4px 16px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger_outline,
|
||||||
|
.mx_AccessibleButton_kind_primary_outline {
|
||||||
|
padding: 3px 16px; // to account for the 1px border
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Checkbox {
|
.mx_Checkbox {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -93,6 +93,10 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.mx_SpaceRoomView_landing) .mx_SpaceFeedbackPrompt {
|
||||||
|
width: $SpaceRoomViewInnerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_buttons {
|
.mx_SpaceRoomView_buttons {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 44px;
|
margin-top: 44px;
|
||||||
|
@ -103,6 +107,10 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
padding: 8px 22px;
|
padding: 8px 22px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.mx_AccessibleButton {
|
||||||
|
border: none; // override default styles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Field {
|
.mx_Field {
|
||||||
|
@ -133,6 +141,44 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 2px 15px 30px $dialog-shadow-color;
|
box-shadow: 2px 15px 30px $dialog-shadow-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
// XXX remove this when spaces leaves Beta
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
top: 32px;
|
||||||
|
}
|
||||||
|
// XXX remove this when spaces leaves Beta
|
||||||
|
.mx_SpaceRoomView_preview_spaceBetaPrompt {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
margin-top: 24px;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 24px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
display: inline;
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: $font-24px;
|
||||||
|
width: 20px;
|
||||||
|
left: 0;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_inviter {
|
.mx_SpaceRoomView_preview_inviter {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -238,7 +284,8 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing_inviteButton {
|
.mx_SpaceRoomView_landing_inviteButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 40px;
|
padding: 4px 18px 4px 40px;
|
||||||
|
line-height: $font-24px;
|
||||||
height: min-content;
|
height: min-content;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
@ -254,6 +301,27 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_settingsButton {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 16px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background: $tertiary-fg-color;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing_topic {
|
.mx_SpaceRoomView_landing_topic {
|
||||||
|
@ -268,87 +336,22 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
background-color: $groupFilterPanel-bg-color;
|
background-color: $groupFilterPanel-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing_adminButtons {
|
|
||||||
margin-top: 24px;
|
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
|
||||||
position: relative;
|
|
||||||
width: 160px;
|
|
||||||
height: 124px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 72px 16px 0;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid $input-border-color;
|
|
||||||
margin-right: 28px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: $font-14px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: bottom;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(141, 151, 165, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before, &::after {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
left: 16px;
|
|
||||||
top: 16px;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
mask-position: center;
|
|
||||||
mask-size: 30px;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
background: #ffffff; // white icon fill
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_SpaceRoomView_landing_addButton {
|
|
||||||
&::before {
|
|
||||||
background-color: #ac3ba8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_SpaceRoomView_landing_createButton {
|
|
||||||
&::before {
|
|
||||||
background-color: #368bd6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_SpaceRoomView_landing_settingsButton {
|
|
||||||
&::before {
|
|
||||||
background-color: #5c56f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_SearchBox {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceFeedbackPrompt {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
// hide the HR as we have our own
|
||||||
|
& + hr {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_privateScope {
|
.mx_SpaceRoomView_privateScope {
|
||||||
.mx_AccessibleButton {
|
> .mx_AccessibleButton {
|
||||||
@mixin SpacePillButton;
|
@mixin SpacePillButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +365,23 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_inviteTeammates {
|
.mx_SpaceRoomView_inviteTeammates {
|
||||||
|
// XXX remove this when spaces leaves Beta
|
||||||
|
.mx_SpaceRoomView_inviteTeammates_betaDisclaimer {
|
||||||
|
padding: 58px 16px 16px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
max-width: $SpaceRoomViewInnerWidth;
|
||||||
|
margin: 20px 0 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_inviteTeammates_buttons {
|
.mx_SpaceRoomView_inviteTeammates_buttons {
|
||||||
color: $secondary-fg-color;
|
color: $secondary-fg-color;
|
||||||
margin-top: 28px;
|
margin-top: 28px;
|
||||||
|
@ -443,3 +463,66 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceFeedbackPrompt {
|
||||||
|
margin-top: 18px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
> hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid $input-border-color;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 32px;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 2px;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
color: $accent-color;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 0 0 24px;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
background-color: $accent-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/chat-bubbles.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
114
res/css/views/beta/_BetaCard.scss
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_BetaCard {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
background-color: $settings-profile-placeholder-bg-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
.mx_BetaCard_title {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
margin: 4px 0 14px;
|
||||||
|
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BetaCard_caption {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-20px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: block;
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 7px 40px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BetaCard_disclaimer {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> img {
|
||||||
|
margin: auto 0 auto 20px;
|
||||||
|
width: 300px;
|
||||||
|
object-fit: contain;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
background-color: $accent-color-alt;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 15px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
|
||||||
|
&.mx_BetaCard_betaPill_clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pulse-color: $accent-color-alt;
|
||||||
|
$dot-size: 12px;
|
||||||
|
|
||||||
|
.mx_BetaDot {
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 10px;
|
||||||
|
height: $dot-size;
|
||||||
|
width: $dot-size;
|
||||||
|
transform: scale(1);
|
||||||
|
background: rgba($pulse-color, 1);
|
||||||
|
box-shadow: 0 0 0 0 rgba($pulse-color, 1);
|
||||||
|
animation: mx_Beta_bluePulse 2s infinite;
|
||||||
|
animation-iteration-count: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mx_Beta_bluePulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 0 0 0 rgba($pulse-color, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 10px rgba($pulse-color, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 0 0 0 rgba($pulse-color, 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,7 +54,8 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
// we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling
|
||||||
|
.mx_DecoratedRoomAvatar {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +76,124 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpace_section_spaces {
|
.mx_AddExistingToSpace_section_spaces {
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
.mx_BaseAvatar_image {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_section_experimental {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 8px 8px 8px 42px;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: calc(50% - 8px); // vertical centering
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_footer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
.mx_ProgressBar {
|
||||||
|
height: 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@mixin ProgressBarBorderRadius 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_progressText {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_error {
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_errorHeading {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_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_AddExistingToSpace_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 {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpaceDialog {
|
.mx_AddExistingToSpaceDialog {
|
||||||
|
@ -101,7 +216,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
.mx_BaseAvatar {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin: 5px 16px 5px 5px;
|
margin: auto 16px auto 5px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,44 +275,7 @@ 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 {
|
.mx_AddExistingToSpace {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpaceDialog_footer {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 32px;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
flex-grow: 1;
|
|
||||||
font-size: $font-14px;
|
|
||||||
line-height: $font-15px;
|
|
||||||
font-weight: $font-semi-bold;
|
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
|
||||||
font-size: inherit;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_link {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
30
res/css/views/dialogs/_BetaFeedbackDialog.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_BetaFeedbackDialog {
|
||||||
|
.mx_BetaFeedbackDialog_subheading {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-20px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
}
|
26
res/css/views/dialogs/_UntrustedDeviceDialog.scss
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_UntrustedDeviceDialog {
|
||||||
|
.mx_Dialog_title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mx_E2EIcon {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,12 +76,16 @@ limitations under the License.
|
||||||
border: 1px solid $button-danger-bg-color;
|
border: 1px solid $button-danger-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled,
|
.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
|
||||||
.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
|
|
||||||
color: $button-danger-disabled-fg-color;
|
color: $button-danger-disabled-fg-color;
|
||||||
background-color: $button-danger-disabled-bg-color;
|
background-color: $button-danger-disabled-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
|
||||||
|
color: $button-danger-disabled-bg-color;
|
||||||
|
border-color: $button-danger-disabled-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_danger_sm {
|
.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_danger_sm {
|
||||||
padding: 5px 12px;
|
padding: 5px 12px;
|
||||||
color: $button-danger-fg-color;
|
color: $button-danger-fg-color;
|
||||||
|
|
|
@ -18,7 +18,11 @@ limitations under the License.
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_InlineSpinner_spin img {
|
.mx_InlineSpinner img, .mx_InlineSpinner_icon {
|
||||||
margin: 0px 6px;
|
margin: 0px 6px;
|
||||||
vertical-align: -3px;
|
vertical-align: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InlineSpinner_icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ limitations under the License.
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before, &::after {
|
.mx_MiniAvatarUploader_indicator {
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
@ -37,15 +36,15 @@ limitations under the License.
|
||||||
|
|
||||||
right: -6px;
|
right: -6px;
|
||||||
bottom: -6px;
|
bottom: -6px;
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
.mx_MiniAvatarUploader_cameraIcon {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
background-color: $secondary-fg-color;
|
background-color: $secondary-fg-color;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
|
@ -53,11 +52,6 @@ limitations under the License.
|
||||||
mask-size: 16px;
|
mask-size: 16px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_MiniAvatarUploader_busy::after {
|
|
||||||
background: url("$(res)/img/spinner.gif") no-repeat center;
|
|
||||||
background-size: 80%;
|
|
||||||
mask: unset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ progress.mx_ProgressBar {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
@mixin ProgressBarBorderRadius "6px";
|
@mixin ProgressBarBorderRadius 6px;
|
||||||
@mixin ProgressBarColour $progressbar-fg-color;
|
@mixin ProgressBarColour $progressbar-fg-color;
|
||||||
@mixin ProgressBarBgColour $progressbar-bg-color;
|
@mixin ProgressBarBgColour $progressbar-bg-color;
|
||||||
::-webkit-progress-value {
|
::-webkit-progress-value {
|
||||||
|
|
|
@ -26,3 +26,19 @@ limitations under the License.
|
||||||
.mx_MatrixChat_middlePanel .mx_Spinner {
|
.mx_MatrixChat_middlePanel .mx_Spinner {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotateZ(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotateZ(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Spinner_icon {
|
||||||
|
background-color: $primary-fg-color;
|
||||||
|
mask: url('$(res)/img/spinner.svg');
|
||||||
|
mask-size: contain;
|
||||||
|
animation: 1.1s steps(12, end) infinite spin;
|
||||||
|
}
|
||||||
|
|
|
@ -61,9 +61,9 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MFileBody_info {
|
.mx_MFileBody_info {
|
||||||
background-color: $message-body-panel-bg-color;
|
background-color: $message-body-panel-bg-color;
|
||||||
border-radius: 4px;
|
border-radius: 12px;
|
||||||
width: 270px;
|
width: 243px; // same width as a playable voice message, accounting for padding
|
||||||
padding: 8px;
|
padding: 6px 12px;
|
||||||
color: $message-body-panel-fg-color;
|
color: $message-body-panel-fg-color;
|
||||||
|
|
||||||
.mx_MFileBody_info_icon {
|
.mx_MFileBody_info_icon {
|
||||||
|
@ -82,7 +82,7 @@ limitations under the License.
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: cover;
|
mask-size: cover;
|
||||||
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
||||||
background-color: $message-body-panel-fg-color;
|
background-color: $message-body-panel-icon-fg-color;
|
||||||
width: 13px;
|
width: 13px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
|
|
||||||
|
|
19
res/css/views/messages/_MVoiceMessageBody.scss
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_MVoiceMessageBody {
|
||||||
|
display: inline-block; // makes the playback controls magically line up
|
||||||
|
}
|
|
@ -85,6 +85,7 @@ limitations under the License.
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
mask-size: 18px;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
background-color: $message-action-bar-fg-color;
|
background-color: $message-action-bar-fg-color;
|
||||||
|
|
|
@ -17,18 +17,56 @@ limitations under the License.
|
||||||
.mx_ReactionsRow {
|
.mx_ReactionsRow {
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
|
|
||||||
|
.mx_ReactionsRow_addReactionButton {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
visibility: hidden; // show on hover of the .mx_EventTile
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
mask-size: 16px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_ReactionsRow_addReactionButton_active {
|
||||||
|
visibility: visible; // keep showing whilst the context menu is shown
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover, &.mx_ReactionsRow_addReactionButton_active {
|
||||||
|
&::before {
|
||||||
|
background-color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile:hover .mx_ReactionsRow_addReactionButton {
|
||||||
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ReactionsRow_showAll {
|
.mx_ReactionsRow_showAll {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: $font-10px;
|
font-size: $font-12px;
|
||||||
font-weight: 600;
|
line-height: $font-20px;
|
||||||
margin-left: 6px;
|
margin-left: 4px;
|
||||||
vertical-align: top;
|
vertical-align: middle;
|
||||||
|
|
||||||
&:hover,
|
&:link, &:visited {
|
||||||
&:link,
|
color: $tertiary-fg-color;
|
||||||
&:visited {
|
}
|
||||||
color: $accent-color;
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,15 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_ReactionsRowButton {
|
.mx_ReactionsRowButton {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
line-height: $font-21px;
|
line-height: $font-20px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
padding: 0 6px;
|
padding: 1px 6px;
|
||||||
border: 1px solid $reaction-row-button-border-color;
|
border: 1px solid $reaction-row-button-border-color;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: $reaction-row-button-bg-color;
|
background-color: $reaction-row-button-bg-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: $reaction-row-button-hover-border-color;
|
border-color: $reaction-row-button-hover-border-color;
|
||||||
|
@ -34,6 +35,10 @@ limitations under the License.
|
||||||
border-color: $reaction-row-button-selected-border-color;
|
border-color: $reaction-row-button-selected-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_AccessibleButton_disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ReactionsRowButton_content {
|
.mx_ReactionsRowButton_content {
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -36,6 +36,7 @@ limitations under the License.
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_avatar {
|
.mx_RoomSummaryCard_avatar {
|
||||||
|
|
|
@ -18,8 +18,8 @@ limitations under the License.
|
||||||
margin: 40px 0 48px 64px;
|
margin: 40px 0 48px 64px;
|
||||||
|
|
||||||
.mx_MiniAvatarUploader_hasAvatar:not(.mx_MiniAvatarUploader_busy):not(:hover) {
|
.mx_MiniAvatarUploader_hasAvatar:not(.mx_MiniAvatarUploader_busy):not(:hover) {
|
||||||
&::before, &::after {
|
.mx_MiniAvatarUploader_indicator {
|
||||||
content: unset;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ limitations under the License.
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 32px;
|
border-radius: 8px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -114,6 +114,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomSublist_auxButton:hover,
|
||||||
|
.mx_RoomSublist_menuButton:hover {
|
||||||
|
background: $roomlist-button-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
// Hide the menu button by default
|
// Hide the menu button by default
|
||||||
.mx_RoomSublist_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
|
@ -39,23 +39,25 @@ limitations under the License.
|
||||||
width: 14px; // w&h are size of icon
|
width: 14px; // w&h are size of icon
|
||||||
height: 18px;
|
height: 18px;
|
||||||
vertical-align: middle;
|
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: $muted-fg-color;
|
background-color: $voice-record-icon-color;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VoiceMessagePrimaryContainer {
|
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
|
||||||
// Note: remaining class properties are in the PlayerContainer CSS.
|
// Note: remaining class properties are in the PlayerContainer CSS.
|
||||||
|
|
||||||
margin: 6px; // force the composer area to put a gutter around us
|
margin: 6px; // force the composer area to put a gutter around us
|
||||||
margin-right: 12px; // isolate from stop button
|
margin-right: 12px; // isolate from stop/send button
|
||||||
|
|
||||||
position: relative; // important for the live circle
|
position: relative; // important for the live circle
|
||||||
|
|
||||||
&.mx_VoiceRecordComposerTile_recording {
|
&.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 {
|
&::before {
|
||||||
animation: recording-pulse 2s infinite;
|
animation: recording-pulse 2s infinite;
|
||||||
|
@ -65,8 +67,8 @@ limitations under the License.
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 8px;
|
left: 12px; // 12px from the left edge for container padding
|
||||||
top: 16px; // vertically center
|
top: 18px; // vertically center (middle align with clock)
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,3 +22,34 @@ limitations under the License.
|
||||||
.mx_HelpUserSettingsTab span.mx_AccessibleButton {
|
.mx_HelpUserSettingsTab span.mx_AccessibleButton {
|
||||||
word-break: break-word;
|
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;
|
||||||
|
}
|
||||||
|
|
25
res/css/views/settings/tabs/user/_LabsUserSettingsTab.scss
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_LabsUserSettingsTab {
|
||||||
|
.mx_SettingsTab_section {
|
||||||
|
margin-top: 32px;
|
||||||
|
|
||||||
|
.mx_SettingsFlag {
|
||||||
|
margin-right: 0; // remove right margin to align with beta cards
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ $spacePanelWidth: 71px;
|
||||||
width: 480px;
|
width: 480px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
> h2 {
|
> h2 {
|
||||||
|
@ -44,6 +45,13 @@ $spacePanelWidth: 71px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX remove this when spaces leaves Beta
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceCreateMenuType {
|
.mx_SpaceCreateMenuType {
|
||||||
@mixin SpacePillButton;
|
@mixin SpacePillButton;
|
||||||
}
|
}
|
||||||
|
@ -59,7 +67,7 @@ $spacePanelWidth: 71px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: $theme-button-bg-color;
|
background-color: $roomlist-button-bg-color;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
@ -70,7 +78,7 @@ $spacePanelWidth: 71px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background-color: $muted-fg-color;
|
background-color: $tertiary-fg-color;
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: 2px 3px;
|
mask-position: 2px 3px;
|
||||||
|
|
|
@ -19,12 +19,12 @@ limitations under the License.
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
background-color: $primary-bg-color;
|
background-color: $voice-playback-button-bg-color;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute; // sizing varies by icon
|
position: absolute; // sizing varies by icon
|
||||||
background-color: $muted-fg-color;
|
background-color: $voice-playback-button-fg-color;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ limitations under the License.
|
||||||
|
|
||||||
// Container for live recording and playback controls
|
// Container for live recording and playback controls
|
||||||
.mx_VoiceMessagePrimaryContainer {
|
.mx_VoiceMessagePrimaryContainer {
|
||||||
padding: 6px; // makes us 4px taller than the send/stop button
|
// 7px top and bottom for visual design. 12px left & right, but the waveform (right)
|
||||||
padding-right: 5px; // there's 1px from the waveform itself, so account for that
|
// has a 1px padding on it that we want to account for.
|
||||||
|
padding: 7px 12px 7px 11px;
|
||||||
background-color: $voice-record-waveform-bg-color;
|
background-color: $voice-record-waveform-bg-color;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
||||||
|
@ -30,11 +31,9 @@ limitations under the License.
|
||||||
|
|
||||||
color: $voice-record-waveform-fg-color;
|
color: $voice-record-waveform-fg-color;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
.mx_Waveform {
|
.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 {
|
.mx_Waveform_bar {
|
||||||
background-color: $voice-record-waveform-incomplete-fg-color;
|
background-color: $voice-record-waveform-incomplete-fg-color;
|
||||||
|
|
||||||
|
@ -47,8 +46,8 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Clock {
|
.mx_Clock {
|
||||||
padding-right: 4px; // isolate from waveform
|
width: $font-42px; // we're not using a monospace font, so fake it
|
||||||
padding-left: 8px; // isolate from live circle
|
padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended.
|
||||||
width: 40px; // we're not using a monospace font, so fake it
|
padding-left: 8px; // isolate from recording circle / play control
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,14 +65,17 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CallView_voice {
|
.mx_CallView_content {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CallView_voice {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
background-color: $inverted-bg-color;
|
background-color: $inverted-bg-color;
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CallView_voice_avatarsContainer {
|
.mx_CallView_voice_avatarsContainer {
|
||||||
|
@ -109,9 +112,7 @@ limitations under the License.
|
||||||
.mx_CallView_video {
|
.mx_CallView_video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,21 +14,37 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.mx_VideoFeed_voice {
|
||||||
|
// We don't want to collide with the call controls that have 52px of height
|
||||||
|
padding-bottom: 52px;
|
||||||
|
background-color: $inverted-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.mx_VideoFeed_remote {
|
.mx_VideoFeed_remote {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.mx_VideoFeed_video {
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
z-index: 50;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VideoFeed_local {
|
.mx_VideoFeed_local {
|
||||||
width: 25%;
|
max-width: 25%;
|
||||||
height: 25%;
|
max-height: 25%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&.mx_VideoFeed_video {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VideoFeed_mirror {
|
.mx_VideoFeed_mirror {
|
||||||
|
|
BIN
res/img/betas/spaces.png
Normal file
After Width: | Height: | Size: 380 KiB |
|
@ -1,7 +1,7 @@
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<mask id="path-1-inside-1" fill="white">
|
<mask id="path-1-inside-1" fill="white">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20ZM13.389 11.7659C13.6794 11.3162 14.2793 11.187 14.729 11.4774C15.1788 11.7677 15.308 12.3676 15.0176 12.8174C13.9565 14.461 12.1059 15.5526 9.99965 15.5526C7.89343 15.5526 6.04281 14.461 4.98167 12.8174C4.69133 12.3677 4.82053 11.7677 5.27025 11.4774C5.71997 11.187 6.31991 11.3162 6.61025 11.7659C7.32946 12.88 8.57908 13.6141 9.99965 13.6141C11.4202 13.6141 12.6698 12.88 13.389 11.7659ZM8 8C8 8.82843 7.44036 9.5 6.75 9.5C6.05964 9.5 5.5 8.82843 5.5 8C5.5 7.17157 6.05964 6.5 6.75 6.5C7.44036 6.5 8 7.17157 8 8ZM13.25 9.5C13.9404 9.5 14.5 8.82843 14.5 8C14.5 7.17157 13.9404 6.5 13.25 6.5C12.5596 6.5 12 7.17157 12 8C12 8.82843 12.5596 9.5 13.25 9.5Z"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20ZM7.99989 7.5C7.99989 8.33 7.32989 9 6.49989 9C5.66989 9 4.99989 8.33 4.99989 7.5C4.99989 6.67 5.66989 6 6.49989 6C7.32989 6 7.99989 6.67 7.99989 7.5ZM14.9999 7.5C14.9999 8.33 14.3299 9 13.4999 9C12.6699 9 11.9999 8.33 11.9999 7.5C11.9999 6.67 12.6699 6 13.4999 6C14.3299 6 14.9999 6.67 14.9999 7.5ZM9.99989 15.5C12.3299 15.5 14.3099 14.04 15.1099 12H4.88989C5.68989 14.04 7.66989 15.5 9.99989 15.5Z"/>
|
||||||
</mask>
|
</mask>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20ZM13.389 11.7659C13.6794 11.3162 14.2793 11.187 14.729 11.4774C15.1788 11.7677 15.308 12.3676 15.0176 12.8174C13.9565 14.461 12.1059 15.5526 9.99965 15.5526C7.89343 15.5526 6.04281 14.461 4.98167 12.8174C4.69133 12.3677 4.82053 11.7677 5.27025 11.4774C5.71997 11.187 6.31991 11.3162 6.61025 11.7659C7.32946 12.88 8.57908 13.6141 9.99965 13.6141C11.4202 13.6141 12.6698 12.88 13.389 11.7659ZM8 8C8 8.82843 7.44036 9.5 6.75 9.5C6.05964 9.5 5.5 8.82843 5.5 8C5.5 7.17157 6.05964 6.5 6.75 6.5C7.44036 6.5 8 7.17157 8 8ZM13.25 9.5C13.9404 9.5 14.5 8.82843 14.5 8C14.5 7.17157 13.9404 6.5 13.25 6.5C12.5596 6.5 12 7.17157 12 8C12 8.82843 12.5596 9.5 13.25 9.5Z" fill="black"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20ZM7.99989 7.5C7.99989 8.33 7.32989 9 6.49989 9C5.66989 9 4.99989 8.33 4.99989 7.5C4.99989 6.67 5.66989 6 6.49989 6C7.32989 6 7.99989 6.67 7.99989 7.5ZM14.9999 7.5C14.9999 8.33 14.3299 9 13.4999 9C12.6699 9 11.9999 8.33 11.9999 7.5C11.9999 6.67 12.6699 6 13.4999 6C14.3299 6 14.9999 6.67 14.9999 7.5ZM9.99989 15.5C12.3299 15.5 14.3099 14.04 15.1099 12H4.88989C5.68989 14.04 7.66989 15.5 9.99989 15.5Z" fill="#737D8C"/>
|
||||||
<path d="M14.1748 11.4164C14.0254 11.6478 14.0919 11.9565 14.3233 12.1059C14.5546 12.2553 14.8633 12.1888 15.0127 11.9574L14.1748 11.4164ZM15.148 11.7479C15.2974 11.5165 15.2309 11.2078 14.9995 11.0584C14.7681 10.909 14.4595 10.9755 14.3101 11.2069L15.148 11.7479ZM15.0127 11.9574L15.148 11.7479L14.3101 11.2069L14.1748 11.4164L15.0127 11.9574ZM14.729 11.4774L15.27 10.6394L15.27 10.6394L14.729 11.4774ZM13.389 11.7659L12.5511 11.225V11.225L13.389 11.7659ZM15.0176 12.8174L14.1797 12.2764V12.2764L15.0176 12.8174ZM4.98167 12.8174L5.8196 12.2764L5.8196 12.2764L4.98167 12.8174ZM5.27025 11.4774L4.72928 10.6394L4.72928 10.6394L5.27025 11.4774ZM6.61025 11.7659L5.77233 12.3069L6.61025 11.7659ZM19.0026 10C19.0026 14.972 14.972 19.0026 10 19.0026V20.9974C16.0737 20.9974 20.9974 16.0737 20.9974 10H19.0026ZM10 0.997378C14.972 0.997378 19.0026 5.02799 19.0026 10H20.9974C20.9974 3.92632 16.0737 -0.997378 10 -0.997378V0.997378ZM0.997378 10C0.997378 5.02799 5.02799 0.997378 10 0.997378V-0.997378C3.92632 -0.997378 -0.997378 3.92632 -0.997378 10H0.997378ZM10 19.0026C5.02799 19.0026 0.997378 14.972 0.997378 10H-0.997378C-0.997378 16.0737 3.92632 20.9974 10 20.9974V19.0026ZM15.27 10.6394C14.3575 10.0503 13.1402 10.3125 12.5511 11.225L14.227 12.3069C14.2259 12.3086 14.2229 12.312 14.2187 12.315C14.215 12.3174 14.2118 12.3186 14.2092 12.3192C14.2067 12.3197 14.2033 12.32 14.1989 12.3192C14.1939 12.3183 14.1898 12.3164 14.1881 12.3153L15.27 10.6394ZM15.8555 13.3583C16.4447 12.4458 16.1825 11.2286 15.27 10.6394L14.1881 12.3153C14.1863 12.3142 14.1829 12.3113 14.18 12.307C14.1775 12.3033 14.1764 12.3001 14.1758 12.2976C14.1753 12.2951 14.175 12.2917 14.1758 12.2873C14.1767 12.2822 14.1786 12.2781 14.1797 12.2764L15.8555 13.3583ZM9.99965 16.55C12.4589 16.55 14.6186 15.2742 15.8555 13.3583L14.1797 12.2764C13.2943 13.6478 11.7528 14.5552 9.99965 14.5552V16.55ZM4.14375 13.3583C5.38067 15.2742 7.54041 16.55 9.99965 16.55V14.5552C8.24645 14.5552 6.70495 13.6478 5.8196 12.2764L4.14375 13.3583ZM4.72928 10.6394C3.81679 11.2286 3.55464 12.4458 4.14375 13.3583L5.8196 12.2764C5.8207 12.2781 5.82261 12.2822 5.8235 12.2873C5.82426 12.2917 5.824 12.2951 5.82346 12.2976C5.82292 12.3001 5.82175 12.3033 5.81926 12.307C5.81635 12.3113 5.81294 12.3142 5.81122 12.3153L4.72928 10.6394ZM7.44818 11.225C6.85907 10.3125 5.64178 10.0503 4.72928 10.6394L5.81122 12.3153C5.8095 12.3164 5.80542 12.3183 5.80034 12.3192C5.79597 12.32 5.79256 12.3197 5.79004 12.3192C5.78752 12.3186 5.7843 12.3174 5.78064 12.315C5.77637 12.3121 5.77344 12.3086 5.77233 12.3069L7.44818 11.225ZM9.99965 12.6167C8.93153 12.6167 7.99128 12.0662 7.44818 11.225L5.77233 12.3069C6.66764 13.6937 8.22663 14.6115 9.99965 14.6115V12.6167ZM12.5511 11.225C12.008 12.0662 11.0678 12.6167 9.99965 12.6167V14.6115C11.7727 14.6115 13.3316 13.6937 14.227 12.3069L12.5511 11.225ZM6.75 10.4974C8.15374 10.4974 8.99738 9.20127 8.99738 8H7.00262C7.00262 8.19305 6.93691 8.33907 6.86768 8.42215C6.80022 8.50311 6.75428 8.50262 6.75 8.50262V10.4974ZM4.50262 8C4.50262 9.20127 5.34626 10.4974 6.75 10.4974V8.50262C6.74572 8.50262 6.69978 8.50311 6.63232 8.42215C6.56309 8.33907 6.49738 8.19305 6.49738 8H4.50262ZM6.75 5.50262C5.34626 5.50262 4.50262 6.79873 4.50262 8H6.49738C6.49738 7.80695 6.56309 7.66093 6.63232 7.57785C6.69978 7.49689 6.74572 7.49738 6.75 7.49738V5.50262ZM8.99738 8C8.99738 6.79873 8.15374 5.50262 6.75 5.50262V7.49738C6.75428 7.49738 6.80022 7.49689 6.86768 7.57785C6.93691 7.66093 7.00262 7.80695 7.00262 8H8.99738ZM13.5026 8C13.5026 8.19305 13.4369 8.33907 13.3677 8.42215C13.3002 8.50311 13.2543 8.50262 13.25 8.50262V10.4974C14.6537 10.4974 15.4974 9.20127 15.4974 8H13.5026ZM13.25 7.49738C13.2543 7.49738 13.3002 7.49689 13.3677 7.57785C13.4369 7.66093 13.5026 7.80695 13.5026 8H15.4974C15.4974 6.79873 14.6537 5.50262 13.25 5.50262V7.49738ZM12.9974 8C12.9974 7.80695 13.0631 7.66093 13.1323 7.57785C13.1998 7.49689 13.2457 7.49738 13.25 7.49738V5.50262C11.8463 5.50262 11.0026 6.79873 11.0026 8H12.9974ZM13.25 8.50262C13.2457 8.50262 13.1998 8.50311 13.1323 8.42215C13.0631 8.33907 12.9974 8.19305 12.9974 8H11.0026C11.0026 9.20127 11.8463 10.4974 13.25 10.4974V8.50262Z" fill="black" mask="url(#path-1-inside-1)"/>
|
<path d="M15.1099 12L16.0384 12.3641L16.5724 11.0026H15.1099V12ZM4.88989 12V11.0026H3.42744L3.96136 12.3641L4.88989 12ZM19.0026 10C19.0026 14.972 14.972 19.0026 10 19.0026V20.9974C16.0737 20.9974 20.9974 16.0737 20.9974 10H19.0026ZM10 0.997378C14.972 0.997378 19.0026 5.02799 19.0026 10H20.9974C20.9974 3.92632 16.0737 -0.997378 10 -0.997378V0.997378ZM0.997378 10C0.997378 5.02799 5.02799 0.997378 10 0.997378V-0.997378C3.92632 -0.997378 -0.997378 3.92632 -0.997378 10H0.997378ZM10 19.0026C5.02799 19.0026 0.997378 14.972 0.997378 10H-0.997378C-0.997378 16.0737 3.92632 20.9974 10 20.9974V19.0026ZM6.49989 9.99738C7.88073 9.99738 8.99727 8.88084 8.99727 7.5H7.00251C7.00251 7.77916 6.77906 8.00262 6.49989 8.00262V9.99738ZM4.00251 7.5C4.00251 8.88084 5.11906 9.99738 6.49989 9.99738V8.00262C6.22073 8.00262 5.99727 7.77916 5.99727 7.5H4.00251ZM6.49989 5.00262C5.11906 5.00262 4.00251 6.11916 4.00251 7.5H5.99727C5.99727 7.22084 6.22073 6.99738 6.49989 6.99738V5.00262ZM8.99727 7.5C8.99727 6.11916 7.88073 5.00262 6.49989 5.00262V6.99738C6.77906 6.99738 7.00251 7.22084 7.00251 7.5H8.99727ZM13.4999 9.99738C14.8807 9.99738 15.9973 8.88084 15.9973 7.5H14.0025C14.0025 7.77916 13.7791 8.00262 13.4999 8.00262V9.99738ZM11.0025 7.5C11.0025 8.88084 12.1191 9.99738 13.4999 9.99738V8.00262C13.2207 8.00262 12.9973 7.77916 12.9973 7.5H11.0025ZM13.4999 5.00262C12.1191 5.00262 11.0025 6.11916 11.0025 7.5H12.9973C12.9973 7.22084 13.2207 6.99738 13.4999 6.99738V5.00262ZM15.9973 7.5C15.9973 6.11916 14.8807 5.00262 13.4999 5.00262V6.99738C13.7791 6.99738 14.0025 7.22084 14.0025 7.5H15.9973ZM14.1814 11.6359C13.5241 13.3119 11.9006 14.5026 9.99989 14.5026V16.4974C12.7592 16.4974 15.0957 14.7681 16.0384 12.3641L14.1814 11.6359ZM4.88989 12.9974H15.1099V11.0026H4.88989V12.9974ZM9.99989 14.5026C8.09919 14.5026 6.47569 13.3119 5.81842 11.6359L3.96136 12.3641C4.9041 14.7681 7.24059 16.4974 9.99989 16.4974V14.5026Z" fill="#737D8C" mask="url(#path-1-inside-1)"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
@ -1,5 +1,3 @@
|
||||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<ellipse cx="5" cy="5.75" rx="1.5" ry="1.75" fill="black"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M20 1C19.4477 1 19 1.44772 19 2V4H17C16.4477 4 16 4.44771 16 5C16 5.55228 16.4477 6 17 6H19V8C19 8.55228 19.4477 9 20 9C20.5523 9 21 8.55228 21 8V6H23C23.5523 6 24 5.55228 24 5C24 4.44772 23.5523 4 23 4H21V2C21 1.44772 20.5523 1 20 1ZM7 9.5C7 8.67 7.67 8 8.5 8C9.33 8 10 8.67 10 9.5C10 10.33 9.33 11 8.5 11C7.67 11 7 10.33 7 9.5ZM15.5 11C16.33 11 17 10.33 17 9.5C17 8.67 16.33 8 15.5 8C14.67 8 14 8.67 14 9.5C14 10.33 14.67 11 15.5 11ZM12 17.5C14.33 17.5 16.31 16.04 17.11 14H6.89001C7.69001 16.04 9.67001 17.5 12 17.5ZM4 12C4 7.58172 7.58172 4 12 4C12.6108 4 13.2045 4.06827 13.7742 4.1972C14.3129 4.3191 14.8484 3.98125 14.9703 3.44258C15.0922 2.90392 14.7543 2.36843 14.2156 2.24653C13.502 2.08504 12.7603 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 11.7878 21.9934 11.5771 21.9803 11.368C21.9459 10.8168 21.4711 10.3978 20.9199 10.4323C20.3687 10.4667 19.9498 10.9414 19.9842 11.4926C19.9947 11.6603 20 11.8295 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12Z" fill="#737D8C"/>
|
||||||
<ellipse cx="13" cy="5.75" rx="1.5" ry="1.75" fill="black"/>
|
|
||||||
<path d="M4.66558 10.6543C4.47466 10.2867 4.0219 10.1435 3.65431 10.3344C3.28672 10.5253 3.1435 10.9781 3.33442 11.3457L4.66558 10.6543ZM14.678 11.3206C14.8551 10.9462 14.6951 10.4991 14.3206 10.322C13.9462 10.1449 13.4991 10.3049 13.322 10.6794L14.678 11.3206ZM4 11C3.33442 11.3457 3.33458 11.346 3.33475 11.3463C3.33481 11.3464 3.33499 11.3468 3.33512 11.347C3.33537 11.3475 3.33565 11.3481 3.33596 11.3486C3.33657 11.3498 3.33729 11.3512 3.3381 11.3527C3.33972 11.3558 3.34175 11.3596 3.34417 11.3641C3.34901 11.3731 3.35546 11.3849 3.36353 11.3993C3.37966 11.4282 3.40229 11.4677 3.43154 11.5162C3.48998 11.6131 3.57517 11.7467 3.68808 11.9048C3.91323 12.2199 4.25254 12.6377 4.71468 13.056C5.64215 13.8956 7.08135 14.75 9.06977 14.75V13.25C7.54656 13.25 6.45087 12.6044 5.72136 11.944C5.35502 11.6123 5.08532 11.2801 4.90858 11.0327C4.82054 10.9095 4.75657 10.8088 4.71608 10.7416C4.69586 10.7081 4.68159 10.6831 4.67318 10.668C4.66898 10.6605 4.66625 10.6555 4.66499 10.6531C4.66435 10.652 4.66409 10.6515 4.66419 10.6516C4.66424 10.6517 4.66438 10.652 4.66461 10.6524C4.66473 10.6527 4.66487 10.6529 4.66503 10.6532C4.66511 10.6534 4.66525 10.6537 4.66529 10.6537C4.66543 10.654 4.66558 10.6543 4 11ZM9.06977 14.75C11.0611 14.75 12.4696 13.893 13.3669 13.0451C13.8129 12.6236 14.1351 12.2027 14.3471 11.8853C14.4535 11.7261 14.5331 11.5914 14.5875 11.4936C14.6148 11.4446 14.6358 11.4047 14.6508 11.3754C14.6583 11.3608 14.6643 11.3488 14.6688 11.3396C14.6711 11.335 14.673 11.3311 14.6745 11.3279C14.6753 11.3263 14.676 11.3249 14.6765 11.3237C14.6768 11.3231 14.6771 11.3225 14.6774 11.322C14.6775 11.3218 14.6776 11.3214 14.6777 11.3213C14.6779 11.3209 14.678 11.3206 14 11C13.322 10.6794 13.3221 10.6791 13.3223 10.6788C13.3223 10.6787 13.3224 10.6784 13.3225 10.6783C13.3227 10.6779 13.3228 10.6776 13.3229 10.6774C13.3232 10.6769 13.3233 10.6766 13.3234 10.6764C13.3235 10.6762 13.3233 10.6766 13.3228 10.6776C13.3217 10.6798 13.3193 10.6846 13.3156 10.6919C13.3081 10.7066 13.2952 10.7312 13.2768 10.7642C13.24 10.8304 13.1813 10.9301 13.0998 11.0522C12.9361 11.2973 12.6842 11.6264 12.3366 11.9549C11.6466 12.607 10.5901 13.25 9.06977 13.25V14.75Z" fill="black"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 34 KiB |
|
@ -1,141 +1,96 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink" preserveAspectRatio="none" viewBox="0 0 375 375" style="background-color:#FFFFFF00; overflow:visible">
|
<svg
|
||||||
<title>start</title>
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
<!-- Layers -->
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
<!-- Layer: Icon -->
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
<svg x="188" y="187" width="0.01" height="0.01" style ="overflow:visible" opacity="1">
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="188;187.75;187.5"/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="187;187.25;187.5"/>
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
<g transform="scale(1 1)">
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
<g transform="rotate(0)">
|
width="128"
|
||||||
<animateTransform attributeName="transform" calcMode="spline" dur="2" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1"
|
height="128"
|
||||||
repeatCount="indefinite" type="rotate" values="0;180;360"/>
|
viewBox="0 0 33.866666 33.866668"
|
||||||
<svg x="-100" y="-100" width="200" height="200" style ="overflow:visible" opacity="1">
|
version="1.1"
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-100;-117.5;-100"/>
|
id="svg920"
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-100;-117.5;-100"/>
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||||
<animate attributeName="width" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="200;235;200"/>
|
sodipodi:docname="spinner.svg">
|
||||||
<animate attributeName="height" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 1 1;0 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="200;235;200"/>
|
<defs
|
||||||
<g clip-path="">
|
id="defs914" />
|
||||||
<g filter="">
|
<metadata
|
||||||
<!-- Layer: 1024@2x -->
|
id="metadata917">
|
||||||
<svg x="100" y="100" width="0.01" height="0.01" style ="overflow:visible" opacity="1">
|
<rdf:RDF>
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="100;117.5;100"/>
|
<cc:Work
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="100;117.5;100"/>
|
rdf:about="">
|
||||||
<g transform="scale(1 1)">
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<g transform="rotate(0)">
|
<dc:type
|
||||||
<svg x="-100" y="-100" width="200" height="200" style ="overflow:visible" opacity="1">
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-100;-117.5;-100"/>
|
<dc:title />
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-100;-117.5;-100"/>
|
</cc:Work>
|
||||||
<animate attributeName="width" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="200;235;200"/>
|
</rdf:RDF>
|
||||||
<animate attributeName="height" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="200;235;200"/>
|
</metadata>
|
||||||
<g clip-path="">
|
<g
|
||||||
<g filter="">
|
inkscape:label="Layer 1"
|
||||||
<!-- Layer: Path -->
|
inkscape:groupmode="layer"
|
||||||
<svg x="118" y="46" width="0.01" height="0.01" style ="overflow:visible" opacity="1">
|
id="layer1">
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="118;138.65;118"/>
|
<path
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="46;54.05;46"/>
|
style="stroke-width:0;fill-opacity:0.30000001"
|
||||||
<g transform="scale(1 1)">
|
d="M 59,95.605469 V 123 c 0,2.77 2.23,5 5,5 2.77,0 5,-2.23 5,-5 V 95.605469 A 31.999998,31.999998 0 0 1 64,96 31.999998,31.999998 0 0 1 59,95.605469 Z"
|
||||||
<g transform="rotate(0)">
|
transform="scale(0.26458333)"
|
||||||
<svg x="-46" y="-46" width="92" height="92" style ="overflow:visible" opacity="1">
|
id="path2350" />
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
<path
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
style="stroke-width:0;fill-opacity:0.7020452"
|
||||||
<animate attributeName="width" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
d="M 64,0 C 61.23,0 59,2.2300001 59,5 V 32.394531 A 31.999998,31.999998 0 0 1 64,32 31.999998,31.999998 0 0 1 69,32.394531 V 5 C 69,2.2300001 66.77,0 64,0 Z"
|
||||||
<animate attributeName="height" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
transform="scale(0.26458333)"
|
||||||
<g clip-path="">
|
id="rect2283" />
|
||||||
<g filter="">
|
<path
|
||||||
<path d="M0,12c0,-6.627,5.373,-12,12,-12 44.183,0,80,35.817,80,80 0,6.627,-5.373,12,-12,12 -6.627,0,-12,-5.373,-12,-12 0,-30.928,-25.072,-56,-56,-56 -6.627,0,-12,-5.373,-12,-12zM0,12" fill="#0DBD8B" id="path" stroke="#00000000" stroke-dasharray="0" stroke-dashoffset="0" stroke-miterlimit="10" stroke-width="0">
|
style="stroke-width:0;fill-opacity:0.30000001"
|
||||||
<animate attributeName="d" calcMode="spline" dur="2s" fill="freeze" href="#path" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="M0,12c0,-6.627,5.373,-12,12,-12 44.183,0,80,35.817,80,80 0,6.627,-5.373,12,-12,12 -6.627,0,-12,-5.373,-12,-12 0,-30.928,-25.072,-56,-56,-56 -6.627,0,-12,-5.373,-12,-12zM0,12;M0,14.1c0,-7.787,6.313,-14.1,14.1,-14.1 51.915,0,94,42.085,94,94 0,7.787,-6.313,14.1,-14.1,14.1 -7.787,0,-14.1,-6.313,-14.1,-14.1 0,-36.34,-29.46,-65.8,-65.8,-65.8 -7.787,0,-14.1,-6.313,-14.1,-14.1zM0,14.1;M0,12c0,-6.627,5.373,-12,12,-12 44.183,0,80,35.817,80,80 0,6.627,-5.373,12,-12,12 -6.627,0,-12,-5.373,-12,-12 0,-30.928,-25.072,-56,-56,-56 -6.627,0,-12,-5.373,-12,-12zM0,12"/>
|
d="M 43.867188,88.871094 30.169922,112.5957 c -1.385,2.39889 -0.568812,5.44508 1.830078,6.83008 2.39889,1.385 5.445078,0.56881 6.830078,-1.83008 L 52.527344,93.873047 a 31.999998,31.999998 0 0 1 -8.660156,-5.001953 z"
|
||||||
</path>
|
transform="scale(0.26458333)"
|
||||||
|
id="path2346" />
|
||||||
</g>
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.80019373"
|
||||||
|
d="m 93.150391,7.9121094 c -1.599848,0.111837 -3.114844,0.992881 -3.980469,2.4921876 L 75.472656,34.126953 a 31.999998,31.999998 0 0 1 8.660156,5.001953 L 97.830078,15.404297 C 99.215078,13.005407 98.39889,9.9592187 96,8.5742188 95.100416,8.0548438 94.110299,7.8450072 93.150391,7.9121094 Z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="rect2285" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.30000001"
|
||||||
|
d="M 34.126953,75.474609 10.404297,89.169922 C 8.0054066,90.554922 7.1892188,93.60111 8.5742188,96 c 1.3849999,2.39889 4.4311882,3.215078 6.8300782,1.830078 L 39.128906,84.132812 a 31.999998,31.999998 0 0 1 -5.001953,-8.658203 z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="path2342" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.90226436"
|
||||||
|
d="m 115.44531,29.507812 c -0.95991,-0.0671 -1.95002,0.142735 -2.84961,0.66211 L 88.871094,43.867188 a 31.999998,31.999998 0 0 1 5.001953,8.658203 L 117.5957,38.830078 c 2.39889,-1.385 3.21508,-4.431188 1.83008,-6.830078 -0.86562,-1.499306 -2.38062,-2.38035 -3.98047,-2.492188 z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="rect2287" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:1"
|
||||||
|
d="M 95.605469,59 A 31.999998,31.999998 0 0 1 96,64 31.999998,31.999998 0 0 1 95.605469,69 H 123 c 2.77,0 5,-2.23 5,-5 0,-2.77 -2.23,-5 -5,-5 z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="path2338" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.40288368"
|
||||||
|
d="m 5,59 c -2.7699999,0 -5,2.23 -5,5 0,2.77 2.2300001,5 5,5 H 32.394531 A 31.999998,31.999998 0 0 1 32,64 31.999998,31.999998 0 0 1 32.394531,59 Z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="rect2289" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.30000001"
|
||||||
|
d="m 93.873047,75.472656 a 31.999998,31.999998 0 0 1 -5.001953,8.660156 L 112.5957,97.830078 c 2.39889,1.385 5.44508,0.568812 6.83008,-1.830078 1.385,-2.39889 0.56881,-5.445078 -1.83008,-6.830078 z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="path2334" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.49898377"
|
||||||
|
d="M 12.554688,29.507812 C 10.95484,29.61965 9.4398437,30.500694 8.5742188,32 c -1.385,2.39889 -0.5688122,5.445078 1.8300782,6.830078 l 23.722656,13.697266 a 31.999998,31.999998 0 0 1 5.001953,-8.660156 L 15.404297,30.169922 c -0.899584,-0.519375 -1.889701,-0.729212 -2.849609,-0.66211 z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="rect2291" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.30000001"
|
||||||
|
d="m 84.132812,88.871094 a 31.999998,31.999998 0 0 1 -8.658203,5.001953 L 89.169922,117.5957 c 1.385,2.39889 4.431188,3.21508 6.830078,1.83008 2.39889,-1.385 3.215078,-4.43119 1.830078,-6.83008 z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="path2330" />
|
||||||
|
<path
|
||||||
|
style="stroke-width:0;fill-opacity:0.5998317"
|
||||||
|
d="M 34.849609,7.9121094 C 33.889701,7.8450072 32.899584,8.0548438 32,8.5742188 29.60111,9.9592187 28.784922,13.005407 30.169922,15.404297 l 13.697266,23.724609 a 31.999998,31.999998 0 0 1 8.658203,-5.001953 L 38.830078,10.404297 C 37.964453,8.9049904 36.449457,8.0239464 34.849609,7.9121094 Z"
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
id="rect2293" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<!-- Layer: Path -->
|
|
||||||
<svg x="82" y="154" width="0.01" height="0.01" style ="overflow:visible" opacity="1">
|
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="82;96.35;82"/>
|
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="154;180.95;154"/>
|
|
||||||
<g transform="scale(1 1)">
|
|
||||||
<g transform="rotate(0)">
|
|
||||||
<svg x="-46" y="-46" width="92" height="92" style ="overflow:visible" opacity="1">
|
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
|
||||||
<animate attributeName="width" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
|
||||||
<animate attributeName="height" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
|
||||||
<g clip-path="">
|
|
||||||
<g filter="">
|
|
||||||
<path d="M92,80c0,6.627,-5.373,12,-12,12 -44.183,0,-80,-35.817,-80,-80 0,-6.627,5.373,-12,12,-12 6.627,0,12,5.373,12,12 0,30.928,25.072,56,56,56 6.627,0,12,5.373,12,12zM92,80" fill="#0DBD8B" id="path_1" stroke="#00000000" stroke-dasharray="0" stroke-dashoffset="0" stroke-miterlimit="10" stroke-width="0">
|
|
||||||
<animate attributeName="d" calcMode="spline" dur="2s" fill="freeze" href="#path_1" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="M92,80c0,6.627,-5.373,12,-12,12 -44.183,0,-80,-35.817,-80,-80 0,-6.627,5.373,-12,12,-12 6.627,0,12,5.373,12,12 0,30.928,25.072,56,56,56 6.627,0,12,5.373,12,12zM92,80;M108.1,94c0,7.787,-6.313,14.1,-14.1,14.1 -51.915,0,-94,-42.085,-94,-94 0,-7.787,6.313,-14.1,14.1,-14.1 7.787,0,14.1,6.313,14.1,14.1 0,36.34,29.46,65.8,65.8,65.8 7.787,0,14.1,6.313,14.1,14.1zM108.1,94;M92,80c0,6.627,-5.373,12,-12,12 -44.183,0,-80,-35.817,-80,-80 0,-6.627,5.373,-12,12,-12 6.627,0,12,5.373,12,12 0,30.928,25.072,56,56,56 6.627,0,12,5.373,12,12zM92,80"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<!-- Layer: Path -->
|
|
||||||
<svg x="46" y="82" width="0.01" height="0.01" style ="overflow:visible" opacity="1">
|
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="46;54.05;46"/>
|
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="82;96.35;82"/>
|
|
||||||
<g transform="scale(1 1)">
|
|
||||||
<g transform="rotate(0)">
|
|
||||||
<svg x="-46" y="-46" width="92" height="92" style ="overflow:visible" opacity="1">
|
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
|
||||||
<animate attributeName="width" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
|
||||||
<animate attributeName="height" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
|
||||||
<g clip-path="">
|
|
||||||
<g filter="">
|
|
||||||
<path d="M12,92c-6.627,0,-12,-5.373,-12,-12 0,-44.183,35.817,-80,80,-80 6.627,0,12,5.373,12,12 0,6.627,-5.373,12,-12,12 -30.928,0,-56,25.072,-56,56 0,6.627,-5.373,12,-12,12zM12,92" fill="#0DBD8B" id="path_2" stroke="#00000000" stroke-dasharray="0" stroke-dashoffset="0" stroke-miterlimit="10" stroke-width="0">
|
|
||||||
<animate attributeName="d" calcMode="spline" dur="2s" fill="freeze" href="#path_2" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="M12,92c-6.627,0,-12,-5.373,-12,-12 0,-44.183,35.817,-80,80,-80 6.627,0,12,5.373,12,12 0,6.627,-5.373,12,-12,12 -30.928,0,-56,25.072,-56,56 0,6.627,-5.373,12,-12,12zM12,92;M14.1,108.1c-7.787,0,-14.1,-6.313,-14.1,-14.1 0,-51.915,42.085,-94,94,-94 7.787,0,14.1,6.313,14.1,14.1 0,7.787,-6.313,14.1,-14.1,14.1 -36.34,0,-65.8,29.46,-65.8,65.8 0,7.787,-6.313,14.1,-14.1,14.1zM14.1,108.1;M12,92c-6.627,0,-12,-5.373,-12,-12 0,-44.183,35.817,-80,80,-80 6.627,0,12,5.373,12,12 0,6.627,-5.373,12,-12,12 -30.928,0,-56,25.072,-56,56 0,6.627,-5.373,12,-12,12zM12,92"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<!-- Layer: Path -->
|
|
||||||
<svg x="154" y="118" width="0.01" height="0.01" style ="overflow:visible" opacity="1">
|
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="154;180.95;154"/>
|
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="118;138.65;118"/>
|
|
||||||
<g transform="scale(1 1)">
|
|
||||||
<g transform="rotate(0)">
|
|
||||||
<svg x="-46" y="-46" width="92" height="92" style ="overflow:visible" opacity="1">
|
|
||||||
<animate attributeName="x" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
|
||||||
<animate attributeName="y" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="-46;-54.05;-46"/>
|
|
||||||
<animate attributeName="width" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
|
||||||
<animate attributeName="height" calcMode="spline" dur="2s" fill="freeze" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="92;108.1;92"/>
|
|
||||||
<g clip-path="">
|
|
||||||
<g filter="">
|
|
||||||
<path d="M80,0c6.627,0,12,5.373,12,12 0,44.183,-35.817,80,-80,80 -6.627,0,-12,-5.373,-12,-12 0,-6.627,5.373,-12,12,-12 30.928,0,56,-25.072,56,-56 0,-6.627,5.373,-12,12,-12zM80,0" fill="#0DBD8B" id="path_3" stroke="#00000000" stroke-dasharray="0" stroke-dashoffset="0" stroke-miterlimit="10" stroke-width="0">
|
|
||||||
<animate attributeName="d" calcMode="spline" dur="2s" fill="freeze" href="#path_3" keySplines="0.42 0 0.58 1;0.42 0 0.58 1" keyTimes="0;0.5;1" repeatCount="indefinite" values="M80,0c6.627,0,12,5.373,12,12 0,44.183,-35.817,80,-80,80 -6.627,0,-12,-5.373,-12,-12 0,-6.627,5.373,-12,12,-12 30.928,0,56,-25.072,56,-56 0,-6.627,5.373,-12,12,-12zM80,0;M94,0c7.787,0,14.1,6.313,14.1,14.1 0,51.915,-42.085,94,-94,94 -7.787,0,-14.1,-6.313,-14.1,-14.1 0,-7.787,6.313,-14.1,14.1,-14.1 36.34,0,65.8,-29.46,65.8,-65.8 0,-7.787,6.313,-14.1,14.1,-14.1zM94,0;M80,0c6.627,0,12,5.373,12,12 0,44.183,-35.817,80,-80,80 -6.627,0,-12,-5.373,-12,-12 0,-6.627,5.373,-12,12,-12 30.928,0,56,-25.072,56,-56 0,-6.627,5.373,-12,12,-12zM80,0"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 5.1 KiB |
|
@ -9,6 +9,7 @@ $header-panel-text-primary-color: #B9BEC6;
|
||||||
$header-panel-text-secondary-color: #c8c8cd;
|
$header-panel-text-secondary-color: #c8c8cd;
|
||||||
$text-primary-color: #ffffff;
|
$text-primary-color: #ffffff;
|
||||||
$text-secondary-color: #B9BEC6;
|
$text-secondary-color: #B9BEC6;
|
||||||
|
$quaternary-fg-color: #6F7882;
|
||||||
$search-bg-color: #181b21;
|
$search-bg-color: #181b21;
|
||||||
$search-placeholder-color: #61708b;
|
$search-placeholder-color: #61708b;
|
||||||
$room-highlight-color: #343a46;
|
$room-highlight-color: #343a46;
|
||||||
|
@ -205,9 +206,18 @@ $breadcrumb-placeholder-bg-color: #272c35;
|
||||||
|
|
||||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
$message-body-panel-bg-color: #21262c82;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #8e99a4;
|
$message-body-panel-bg-color: #394049; // "Dark Tile"
|
||||||
$message-body-panel-fg-color: $primary-fg-color;
|
$message-body-panel-icon-fg-color: #21262C; // "Separator"
|
||||||
|
$message-body-panel-icon-bg-color: $tertiary-fg-color;
|
||||||
|
|
||||||
|
$voice-record-stop-border-color: $quaternary-fg-color;
|
||||||
|
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
||||||
|
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
||||||
|
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
||||||
|
$voice-record-icon-color: $quaternary-fg-color;
|
||||||
|
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
||||||
|
$voice-playback-button-fg-color: $message-body-panel-icon-fg-color;
|
||||||
|
|
||||||
// Appearance tab colors
|
// Appearance tab colors
|
||||||
$appearance-tab-border-color: $room-highlight-color;
|
$appearance-tab-border-color: $room-highlight-color;
|
||||||
|
|
|
@ -200,9 +200,19 @@ $breadcrumb-placeholder-bg-color: #272c35;
|
||||||
|
|
||||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
$message-body-panel-bg-color: #21262c82;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #8e99a4;
|
$message-body-panel-bg-color: #394049;
|
||||||
$message-body-panel-fg-color: $primary-fg-color;
|
$message-body-panel-icon-fg-color: $primary-bg-color;
|
||||||
|
$message-body-panel-icon-bg-color: $secondary-fg-color;
|
||||||
|
|
||||||
|
// See non-legacy dark for variable information
|
||||||
|
$voice-record-stop-border-color: #6F7882;
|
||||||
|
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
||||||
|
$voice-record-waveform-fg-color: $message-body-panel-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: #21262C;
|
||||||
|
|
||||||
// Appearance tab colors
|
// Appearance tab colors
|
||||||
$appearance-tab-border-color: $room-highlight-color;
|
$appearance-tab-border-color: $room-highlight-color;
|
||||||
|
|
|
@ -191,14 +191,6 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%)
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$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;
|
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #fff;
|
$roomtile-selected-bg-color: #fff;
|
||||||
|
@ -331,9 +323,21 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
||||||
|
|
||||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
$message-body-panel-bg-color: #e3e8f082;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #ffffff;
|
$message-body-panel-bg-color: #E3E8F0;
|
||||||
$message-body-panel-fg-color: $muted-fg-color;
|
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
||||||
|
$message-body-panel-icon-bg-color: $primary-bg-color;
|
||||||
|
|
||||||
|
// See non-legacy _light for variable information
|
||||||
|
$voice-record-stop-symbol-color: #ff4b55;
|
||||||
|
$voice-record-live-circle-color: #ff4b55;
|
||||||
|
$voice-record-stop-border-color: #E3E8F0;
|
||||||
|
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
||||||
|
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
||||||
|
$voice-record-waveform-incomplete-fg-color: #C1C6CD;
|
||||||
|
$voice-record-icon-color: $tertiary-fg-color;
|
||||||
|
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
||||||
|
$voice-playback-button-fg-color: $message-body-panel-icon-fg-color;
|
||||||
|
|
||||||
// FontSlider colors
|
// FontSlider colors
|
||||||
$appearance-tab-border-color: $input-darker-bg-color;
|
$appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
|
@ -21,6 +21,7 @@ $notice-primary-bg-color: rgba(255, 75, 85, 0.16);
|
||||||
$primary-fg-color: #2e2f32;
|
$primary-fg-color: #2e2f32;
|
||||||
$secondary-fg-color: #737D8C;
|
$secondary-fg-color: #737D8C;
|
||||||
$tertiary-fg-color: #8D99A5;
|
$tertiary-fg-color: #8D99A5;
|
||||||
|
$quaternary-fg-color: #C1C6CD;
|
||||||
$header-panel-bg-color: #f3f8fd;
|
$header-panel-bg-color: #f3f8fd;
|
||||||
|
|
||||||
// typical text (dark-on-white in light skin)
|
// typical text (dark-on-white in light skin)
|
||||||
|
@ -182,13 +183,6 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%)
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$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
|
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #FFF;
|
$roomtile-selected-bg-color: #FFF;
|
||||||
|
@ -328,9 +322,23 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
||||||
|
|
||||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
$message-body-panel-bg-color: #e3e8f082;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #ffffff;
|
$message-body-panel-bg-color: #E3E8F0; // "Separator"
|
||||||
$message-body-panel-fg-color: $muted-fg-color;
|
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
||||||
|
$message-body-panel-icon-bg-color: $primary-bg-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: $message-body-panel-bg-color;
|
||||||
|
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
||||||
|
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
||||||
|
$voice-record-icon-color: $tertiary-fg-color;
|
||||||
|
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
||||||
|
$voice-playback-button-fg-color: $message-body-panel-icon-fg-color;
|
||||||
|
|
||||||
// FontSlider colors
|
// FontSlider colors
|
||||||
$appearance-tab-border-color: $input-darker-bg-color;
|
$appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
16
src/@types/global.d.ts
vendored
|
@ -42,6 +42,7 @@ import {SpaceStoreClass} from "../stores/SpaceStore";
|
||||||
import TypingStore from "../stores/TypingStore";
|
import TypingStore from "../stores/TypingStore";
|
||||||
import { EventIndexPeg } from "../indexing/EventIndexPeg";
|
import { EventIndexPeg } from "../indexing/EventIndexPeg";
|
||||||
import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
|
import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
|
||||||
|
import PerformanceMonitor from "../performance";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -52,6 +53,9 @@ declare global {
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Needed for Safari, unknown to TypeScript
|
||||||
|
webkitAudioContext: typeof AudioContext;
|
||||||
|
|
||||||
mxContentMessages: ContentMessages;
|
mxContentMessages: ContentMessages;
|
||||||
mxToastStore: ToastStore;
|
mxToastStore: ToastStore;
|
||||||
mxDeviceListener: DeviceListener;
|
mxDeviceListener: DeviceListener;
|
||||||
|
@ -76,6 +80,8 @@ declare global {
|
||||||
mxVoiceRecordingStore: VoiceRecordingStore;
|
mxVoiceRecordingStore: VoiceRecordingStore;
|
||||||
mxTypingStore: TypingStore;
|
mxTypingStore: TypingStore;
|
||||||
mxEventIndexPeg: EventIndexPeg;
|
mxEventIndexPeg: EventIndexPeg;
|
||||||
|
mxPerformanceMonitor: PerformanceMonitor;
|
||||||
|
mxPerformanceEntryNames: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
@ -118,6 +124,16 @@ declare global {
|
||||||
|
|
||||||
interface HTMLAudioElement {
|
interface HTMLAudioElement {
|
||||||
type?: string;
|
type?: string;
|
||||||
|
// sinkId & setSinkId are experimental and typescript doesn't know about them
|
||||||
|
sinkId: string;
|
||||||
|
setSinkId(outputId: string);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HTMLVideoElement {
|
||||||
|
type?: string;
|
||||||
|
// sinkId & setSinkId are experimental and typescript doesn't know about them
|
||||||
|
sinkId: string;
|
||||||
|
setSinkId(outputId: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Element {
|
interface Element {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import {mediaFromMxc} from "./customisations/Media";
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
|
||||||
export type ResizeMethod = "crop" | "scale";
|
export type ResizeMethod = "crop" | "scale";
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
|
||||||
}
|
}
|
||||||
|
|
||||||
// space rooms cannot be DMs so skip the rest
|
// space rooms cannot be DMs so skip the rest
|
||||||
if (room.isSpaceRoom()) return null;
|
if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return null;
|
||||||
|
|
||||||
let otherMember = null;
|
let otherMember = null;
|
||||||
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||||
|
|
|
@ -85,6 +85,7 @@ import { Action } from './dispatcher/actions';
|
||||||
import VoipUserMapper from './VoipUserMapper';
|
import VoipUserMapper from './VoipUserMapper';
|
||||||
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
||||||
import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/randomstring";
|
import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/randomstring";
|
||||||
|
import EventEmitter from 'events';
|
||||||
import SdkConfig from './SdkConfig';
|
import SdkConfig from './SdkConfig';
|
||||||
import { ensureDMExists, findDMForUser } from './createRoom';
|
import { ensureDMExists, findDMForUser } from './createRoom';
|
||||||
|
|
||||||
|
@ -138,22 +139,12 @@ export enum PlaceCallType {
|
||||||
ScreenSharing = 'screensharing',
|
ScreenSharing = 'screensharing',
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRemoteAudioElement(): HTMLAudioElement {
|
export enum CallHandlerEvent {
|
||||||
// this needs to be somewhere at the top of the DOM which
|
CallsChanged = "calls_changed",
|
||||||
// always exists to avoid audio interruptions.
|
CallChangeRoom = "call_change_room",
|
||||||
// Might as well just use DOM.
|
|
||||||
const remoteAudioElement = document.getElementById("remoteAudio") as HTMLAudioElement;
|
|
||||||
if (!remoteAudioElement) {
|
|
||||||
console.error(
|
|
||||||
"Failed to find remoteAudio element - cannot play audio!" +
|
|
||||||
"You need to add an <audio/> to the DOM.",
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return remoteAudioElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CallHandler {
|
export default class CallHandler extends EventEmitter {
|
||||||
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
private calls = new Map<string, MatrixCall>(); // roomId -> call
|
||||||
// Calls started as an attended transfer, ie. with the intention of transferring another
|
// Calls started as an attended transfer, ie. with the intention of transferring another
|
||||||
// call with a different party to this one.
|
// call with a different party to this one.
|
||||||
|
@ -514,6 +505,7 @@ export default class CallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.calls.set(mappedRoomId, newCall);
|
this.calls.set(mappedRoomId, newCall);
|
||||||
|
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||||
this.setCallListeners(newCall);
|
this.setCallListeners(newCall);
|
||||||
this.setCallState(newCall, newCall.state);
|
this.setCallState(newCall, newCall.state);
|
||||||
});
|
});
|
||||||
|
@ -546,10 +538,7 @@ export default class CallHandler {
|
||||||
this.removeCallForRoom(mappedRoomId);
|
this.removeCallForRoom(mappedRoomId);
|
||||||
mappedRoomId = newMappedRoomId;
|
mappedRoomId = newMappedRoomId;
|
||||||
this.calls.set(mappedRoomId, call);
|
this.calls.set(mappedRoomId, call);
|
||||||
dis.dispatch({
|
this.emit(CallHandlerEvent.CallChangeRoom, call);
|
||||||
action: Action.CallChangeRoom,
|
|
||||||
call,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -598,11 +587,6 @@ export default class CallHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCallAudioElement(call: MatrixCall) {
|
|
||||||
const audioElement = getRemoteAudioElement();
|
|
||||||
if (audioElement) call.setRemoteAudioElement(audioElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setCallState(call: MatrixCall, status: CallState) {
|
private setCallState(call: MatrixCall, status: CallState) {
|
||||||
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
|
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
|
||||||
|
|
||||||
|
@ -619,6 +603,7 @@ export default class CallHandler {
|
||||||
|
|
||||||
private removeCallForRoom(roomId: string) {
|
private removeCallForRoom(roomId: string) {
|
||||||
this.calls.delete(roomId);
|
this.calls.delete(roomId);
|
||||||
|
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private showICEFallbackPrompt() {
|
private showICEFallbackPrompt() {
|
||||||
|
@ -679,11 +664,7 @@ export default class CallHandler {
|
||||||
}, null, true);
|
}, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async placeCall(
|
private async placeCall(roomId: string, type: PlaceCallType, transferee: MatrixCall) {
|
||||||
roomId: string, type: PlaceCallType,
|
|
||||||
localElement: HTMLVideoElement, remoteElement: HTMLVideoElement,
|
|
||||||
transferee: MatrixCall,
|
|
||||||
) {
|
|
||||||
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
Analytics.trackEvent('voip', 'placeCall', 'type', type);
|
||||||
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
|
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
|
||||||
|
|
||||||
|
@ -695,22 +676,19 @@ export default class CallHandler {
|
||||||
const call = MatrixClientPeg.get().createCall(mappedRoomId);
|
const call = MatrixClientPeg.get().createCall(mappedRoomId);
|
||||||
|
|
||||||
this.calls.set(roomId, call);
|
this.calls.set(roomId, call);
|
||||||
|
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||||
if (transferee) {
|
if (transferee) {
|
||||||
this.transferees[call.callId] = transferee;
|
this.transferees[call.callId] = transferee;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setCallListeners(call);
|
this.setCallListeners(call);
|
||||||
this.setCallAudioElement(call);
|
|
||||||
|
|
||||||
this.setActiveCallRoomId(roomId);
|
this.setActiveCallRoomId(roomId);
|
||||||
|
|
||||||
if (type === PlaceCallType.Voice) {
|
if (type === PlaceCallType.Voice) {
|
||||||
call.placeVoiceCall();
|
call.placeVoiceCall();
|
||||||
} else if (type === 'video') {
|
} else if (type === 'video') {
|
||||||
call.placeVideoCall(
|
call.placeVideoCall();
|
||||||
remoteElement,
|
|
||||||
localElement,
|
|
||||||
);
|
|
||||||
} else if (type === PlaceCallType.ScreenSharing) {
|
} else if (type === PlaceCallType.ScreenSharing) {
|
||||||
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
|
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
|
||||||
if (screenCapErrorString) {
|
if (screenCapErrorString) {
|
||||||
|
@ -724,13 +702,12 @@ export default class CallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
call.placeScreenSharingCall(
|
call.placeScreenSharingCall(
|
||||||
remoteElement,
|
|
||||||
localElement,
|
|
||||||
async (): Promise<DesktopCapturerSource> => {
|
async (): Promise<DesktopCapturerSource> => {
|
||||||
const {finished} = Modal.createDialog(DesktopCapturerSourcePicker);
|
const {finished} = Modal.createDialog(DesktopCapturerSourcePicker);
|
||||||
const [source] = await finished;
|
const [source] = await finished;
|
||||||
return source;
|
return source;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error("Unknown conf call type: " + type);
|
console.error("Unknown conf call type: " + type);
|
||||||
}
|
}
|
||||||
|
@ -787,17 +764,12 @@ export default class CallHandler {
|
||||||
} else if (members.length === 2) {
|
} else if (members.length === 2) {
|
||||||
console.info(`Place ${payload.type} call in ${payload.room_id}`);
|
console.info(`Place ${payload.type} call in ${payload.room_id}`);
|
||||||
|
|
||||||
this.placeCall(
|
this.placeCall(payload.room_id, payload.type, payload.transferee);
|
||||||
payload.room_id, payload.type, payload.local_element, payload.remote_element,
|
|
||||||
payload.transferee,
|
|
||||||
);
|
|
||||||
} else { // > 2
|
} else { // > 2
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "place_conference_call",
|
action: "place_conference_call",
|
||||||
room_id: payload.room_id,
|
room_id: payload.room_id,
|
||||||
type: payload.type,
|
type: payload.type,
|
||||||
remote_element: payload.remote_element,
|
|
||||||
local_element: payload.local_element,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -833,6 +805,7 @@ export default class CallHandler {
|
||||||
|
|
||||||
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
|
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
|
||||||
this.calls.set(mappedRoomId, call)
|
this.calls.set(mappedRoomId, call)
|
||||||
|
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||||
this.setCallListeners(call);
|
this.setCallListeners(call);
|
||||||
|
|
||||||
// get ready to send encrypted events in the room, so if the user does answer
|
// get ready to send encrypted events in the room, so if the user does answer
|
||||||
|
@ -875,7 +848,6 @@ export default class CallHandler {
|
||||||
|
|
||||||
const call = this.calls.get(payload.room_id);
|
const call = this.calls.get(payload.room_id);
|
||||||
call.answer();
|
call.answer();
|
||||||
this.setCallAudioElement(call);
|
|
||||||
this.setActiveCallRoomId(payload.room_id);
|
this.setActiveCallRoomId(payload.room_id);
|
||||||
CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false);
|
CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import {SettingLevel} from "./settings/SettingLevel";
|
import {SettingLevel} from "./settings/SettingLevel";
|
||||||
import {setMatrixCallAudioInput, setMatrixCallAudioOutput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
|
import {setMatrixCallAudioInput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
hasAnyLabeledDevices: async function() {
|
hasAnyLabeledDevices: async function() {
|
||||||
|
@ -50,18 +50,15 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
loadDevices: function() {
|
loadDevices: function() {
|
||||||
const audioOutDeviceId = SettingsStore.getValue("webrtc_audiooutput");
|
|
||||||
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
||||||
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
||||||
|
|
||||||
setMatrixCallAudioOutput(audioOutDeviceId);
|
|
||||||
setMatrixCallAudioInput(audioDeviceId);
|
setMatrixCallAudioInput(audioDeviceId);
|
||||||
setMatrixCallVideoInput(videoDeviceId);
|
setMatrixCallVideoInput(videoDeviceId);
|
||||||
},
|
},
|
||||||
|
|
||||||
setAudioOutput: function(deviceId) {
|
setAudioOutput: function(deviceId) {
|
||||||
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
||||||
setMatrixCallAudioOutput(deviceId);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setAudioInput: function(deviceId) {
|
setAudioInput: function(deviceId) {
|
||||||
|
|
|
@ -148,13 +148,15 @@ function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Failed to add the following room to the group',
|
'Failed to add the following room to the group',
|
||||||
'', ErrorDialog,
|
'',
|
||||||
|
ErrorDialog,
|
||||||
{
|
{
|
||||||
title: _t(
|
title: _t(
|
||||||
"Failed to add the following rooms to %(groupId)s:",
|
"Failed to add the following rooms to %(groupId)s:",
|
||||||
{groupId},
|
{groupId},
|
||||||
),
|
),
|
||||||
description: errorList.join(", "),
|
description: errorList.join(", "),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,8 +422,12 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||||
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
|
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
|
||||||
|
|
||||||
if (SettingsStore.getValue("feature_latex_maths")) {
|
if (SettingsStore.getValue("feature_latex_maths")) {
|
||||||
const phtml = cheerio.load(safeBody,
|
const phtml = cheerio.load(safeBody, {
|
||||||
{ _useHtmlParser2: true, decodeEntities: false })
|
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||||
|
// simplest way to both parse and render using `htmlparser2`.
|
||||||
|
_useHtmlParser2: true,
|
||||||
|
decodeEntities: false,
|
||||||
|
});
|
||||||
// @ts-ignore - The types for `replaceWith` wrongly expect
|
// @ts-ignore - The types for `replaceWith` wrongly expect
|
||||||
// Cheerio instance to be returned.
|
// Cheerio instance to be returned.
|
||||||
phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) {
|
phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) {
|
||||||
|
@ -431,6 +435,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||||
AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')),
|
AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')),
|
||||||
{
|
{
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
|
// @ts-ignore - `e` can be an Element, not just a Node
|
||||||
displayMode: e.name == 'div',
|
displayMode: e.name == 'div',
|
||||||
output: "htmlAndMathml",
|
output: "htmlAndMathml",
|
||||||
});
|
});
|
||||||
|
|
|
@ -331,6 +331,8 @@ export const Notifier = {
|
||||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||||
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
|
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||||
|
|
||||||
|
MatrixClientPeg.get().decryptEventIfNeeded(ev);
|
||||||
|
|
||||||
// If it's an encrypted event and the type is still 'm.room.encrypted',
|
// If it's an encrypted event and the type is still 'm.room.encrypted',
|
||||||
// it hasn't yet been decrypted, so wait until it is.
|
// it hasn't yet been decrypted, so wait until it is.
|
||||||
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
|
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";
|
||||||
import {inviteUsersToRoom} from "./RoomInvite";
|
import {inviteUsersToRoom} from "./RoomInvite";
|
||||||
import { WidgetType } from "./widgets/WidgetType";
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { parseFragment as parseHtml } from "parse5";
|
import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
|
||||||
import BugReportDialog from "./components/views/dialogs/BugReportDialog";
|
import BugReportDialog from "./components/views/dialogs/BugReportDialog";
|
||||||
import { ensureDMExists } from "./createRoom";
|
import { ensureDMExists } from "./createRoom";
|
||||||
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
||||||
|
@ -856,7 +856,7 @@ export const Commands = [
|
||||||
// some superfast regex over the text so we don't have to.
|
// some superfast regex over the text so we don't have to.
|
||||||
const embed = parseHtml(widgetUrl);
|
const embed = parseHtml(widgetUrl);
|
||||||
if (embed && embed.childNodes && embed.childNodes.length === 1) {
|
if (embed && embed.childNodes && embed.childNodes.length === 1) {
|
||||||
const iframe = embed.childNodes[0];
|
const iframe = embed.childNodes[0] as ChildElement;
|
||||||
if (iframe.tagName.toLowerCase() === 'iframe' && iframe.attrs) {
|
if (iframe.tagName.toLowerCase() === 'iframe' && iframe.attrs) {
|
||||||
const srcAttr = iframe.attrs.find(a => a.name === 'src');
|
const srcAttr = iframe.attrs.find(a => a.name === 'src');
|
||||||
console.log("Pulling URL out of iframe (embed code)");
|
console.log("Pulling URL out of iframe (embed code)");
|
||||||
|
|
|
@ -547,17 +547,23 @@ function textForMjolnirEvent(event) {
|
||||||
|
|
||||||
// else the entity !== prevEntity - count as a removal & add
|
// else the entity !== prevEntity - count as a removal & add
|
||||||
if (USER_RULE_TYPES.includes(event.getType())) {
|
if (USER_RULE_TYPES.includes(event.getType())) {
|
||||||
return _t("%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " +
|
return _t(
|
||||||
|
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " +
|
||||||
"%(newGlob)s for %(reason)s",
|
"%(newGlob)s for %(reason)s",
|
||||||
{senderName, oldGlob: prevEntity, newGlob: entity, reason});
|
{senderName, oldGlob: prevEntity, newGlob: entity, reason},
|
||||||
|
);
|
||||||
} else if (ROOM_RULE_TYPES.includes(event.getType())) {
|
} else if (ROOM_RULE_TYPES.includes(event.getType())) {
|
||||||
return _t("%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " +
|
return _t(
|
||||||
|
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " +
|
||||||
"%(newGlob)s for %(reason)s",
|
"%(newGlob)s for %(reason)s",
|
||||||
{senderName, oldGlob: prevEntity, newGlob: entity, reason});
|
{senderName, oldGlob: prevEntity, newGlob: entity, reason},
|
||||||
|
);
|
||||||
} else if (SERVER_RULE_TYPES.includes(event.getType())) {
|
} else if (SERVER_RULE_TYPES.includes(event.getType())) {
|
||||||
return _t("%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " +
|
return _t(
|
||||||
|
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " +
|
||||||
"%(newGlob)s for %(reason)s",
|
"%(newGlob)s for %(reason)s",
|
||||||
{senderName, oldGlob: prevEntity, newGlob: entity, reason});
|
{senderName, oldGlob: prevEntity, newGlob: entity, reason},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown type. We'll say something but we shouldn't end up here.
|
// Unknown type. We'll say something but we shouldn't end up here.
|
||||||
|
|
|
@ -40,6 +40,8 @@ export function eventTriggersUnreadCount(ev) {
|
||||||
return false;
|
return false;
|
||||||
} else if (ev.getType() == 'm.room.server_acl') {
|
} else if (ev.getType() == 'm.room.server_acl') {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (ev.isRedacted()) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return haveTileForEvent(ev);
|
return haveTileForEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
|
||||||
const onKeyDownHandler = useCallback((ev) => {
|
const onKeyDownHandler = useCallback((ev) => {
|
||||||
let handled = false;
|
let handled = false;
|
||||||
// Don't interfere with input default keydown behaviour
|
// Don't interfere with input default keydown behaviour
|
||||||
if (handleHomeEnd && ev.target.tagName !== "INPUT") {
|
if (handleHomeEnd && ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") {
|
||||||
// check if we actually have any items
|
// check if we actually have any items
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
case Key.HOME:
|
case Key.HOME:
|
||||||
|
|
|
@ -310,7 +310,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Please enter your Security Phrase a second time to confirm.",
|
"Enter your Security Phrase a second time to confirm it.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
|
|
|
@ -647,7 +647,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
return <form onSubmit={this._onPassPhraseConfirmNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Enter your recovery passphrase a second time to confirm it.",
|
"Enter your Security Phrase a second time to confirm it.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<Field
|
||||||
|
@ -655,7 +655,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
onChange={this._onPassPhraseConfirmChange}
|
onChange={this._onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
label={_t("Confirm your recovery passphrase")}
|
label={_t("Confirm your Security Phrase")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -170,7 +170,10 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input className='mx_Dialog_primary' type='submit' value={_t('Export')}
|
<input
|
||||||
|
className='mx_Dialog_primary'
|
||||||
|
type='submit'
|
||||||
|
value={_t('Export')}
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
|
|
|
@ -93,7 +93,12 @@ export default class AutocompleteProvider {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ import EmojiProvider from './EmojiProvider';
|
||||||
import NotifProvider from './NotifProvider';
|
import NotifProvider from './NotifProvider';
|
||||||
import {timeout} from "../utils/promise";
|
import {timeout} from "../utils/promise";
|
||||||
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
|
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
|
||||||
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
import SpaceProvider from "./SpaceProvider";
|
||||||
|
|
||||||
export interface ISelectionRange {
|
export interface ISelectionRange {
|
||||||
beginning?: boolean; // whether the selection is in the first block of the editor or not
|
beginning?: boolean; // whether the selection is in the first block of the editor or not
|
||||||
|
@ -56,6 +58,11 @@ const PROVIDERS = [
|
||||||
DuckDuckGoProvider,
|
DuckDuckGoProvider,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// as the spaces feature is device configurable only, and toggling it refreshes the page, we can do this here
|
||||||
|
if (SettingsStore.getValue("feature_spaces")) {
|
||||||
|
PROVIDERS.push(SpaceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
// Providers will get rejected if they take longer than this.
|
// Providers will get rejected if they take longer than this.
|
||||||
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
||||||
|
|
||||||
|
@ -82,15 +89,24 @@ export default class Autocompleter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<IProviderCompletions[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<IProviderCompletions[]> {
|
||||||
/* Note: This intentionally waits for all providers to return,
|
/* Note: This intentionally waits for all providers to return,
|
||||||
otherwise, we run into a condition where new completions are displayed
|
otherwise, we run into a condition where new completions are displayed
|
||||||
while the user is interacting with the list, which makes it difficult
|
while the user is interacting with the list, which makes it difficult
|
||||||
to predict whether an action will actually do what is intended
|
to predict whether an action will actually do what is intended
|
||||||
*/
|
*/
|
||||||
// list of results from each provider, each being a list of completions or null if it times out
|
// list of results from each provider, each being a list of completions or null if it times out
|
||||||
const completionsList: ICompletion[][] = await Promise.all(this.providers.map(provider => {
|
const completionsList: ICompletion[][] = await Promise.all(this.providers.map(async provider => {
|
||||||
return timeout(provider.getCompletions(query, selection, force), null, PROVIDER_COMPLETION_TIMEOUT);
|
return await timeout(
|
||||||
|
provider.getCompletions(query, selection, force, limit),
|
||||||
|
null,
|
||||||
|
PROVIDER_COMPLETION_TIMEOUT,
|
||||||
|
);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// map then filter to maintain the index for the map-operation, for this.providers to line up
|
// map then filter to maintain the index for the map-operation, for this.providers to line up
|
||||||
|
|
|
@ -38,7 +38,12 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force?: boolean,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
const {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (!command) return [];
|
if (!command) return [];
|
||||||
|
|
||||||
|
@ -55,10 +60,11 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
} else {
|
} else {
|
||||||
if (query === '/') {
|
if (query === '/') {
|
||||||
// If they have just entered `/` show everything
|
// If they have just entered `/` show everything
|
||||||
|
// We exclude the limit on purpose to have a comprehensive list
|
||||||
matches = Commands;
|
matches = Commands;
|
||||||
} else {
|
} else {
|
||||||
// otherwise fuzzy match against all of the fields
|
// otherwise fuzzy match against all of the fields
|
||||||
matches = this.matcher.match(command[1]);
|
matches = this.matcher.match(command[1], limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,12 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
|
||||||
|
|
||||||
// Disable autocompletions when composing commands because of various issues
|
// Disable autocompletions when composing commands because of various issues
|
||||||
|
@ -81,7 +86,7 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
this.matcher.setObjects(groups);
|
this.matcher.setObjects(groups);
|
||||||
|
|
||||||
const matchedString = command[0];
|
const matchedString = command[0];
|
||||||
completions = this.matcher.match(matchedString);
|
completions = this.matcher.match(matchedString, limit);
|
||||||
completions = sortBy(completions, [
|
completions = sortBy(completions, [
|
||||||
(c) => score(matchedString, c.groupId),
|
(c) => score(matchedString, c.groupId),
|
||||||
(c) => c.groupId.length,
|
(c) => c.groupId.length,
|
||||||
|
|
|
@ -36,7 +36,12 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
|
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force= false): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
const {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (!query || !command) {
|
if (!query || !command) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -46,7 +51,8 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const results = json.Results.map((result) => {
|
const maxLength = limit > -1 ? limit : json.Results.length;
|
||||||
|
const results = json.Results.slice(0, maxLength).map((result) => {
|
||||||
return {
|
return {
|
||||||
completion: result.Text,
|
completion: result.Text,
|
||||||
component: (
|
component: (
|
||||||
|
|
|
@ -84,7 +84,12 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force?: boolean,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
if (!SettingsStore.getValue("MessageComposerInput.suggestEmoji")) {
|
if (!SettingsStore.getValue("MessageComposerInput.suggestEmoji")) {
|
||||||
return []; // don't give any suggestions if the user doesn't want them
|
return []; // don't give any suggestions if the user doesn't want them
|
||||||
}
|
}
|
||||||
|
@ -93,7 +98,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
const {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (command) {
|
if (command) {
|
||||||
const matchedString = command[0];
|
const matchedString = command[0];
|
||||||
completions = this.matcher.match(matchedString);
|
completions = this.matcher.match(matchedString, limit);
|
||||||
|
|
||||||
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
||||||
completions = completions.concat(this.nameMatcher.match(matchedString));
|
completions = completions.concat(this.nameMatcher.match(matchedString));
|
||||||
|
|
|
@ -33,7 +33,12 @@ export default class NotifProvider extends AutocompleteProvider {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force= false): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {removeHiddenChars} from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
interface IOptions<T extends {}> {
|
interface IOptions<T extends {}> {
|
||||||
keys: Array<string | keyof T>;
|
keys: Array<string | keyof T>;
|
||||||
funcs?: Array<(T) => string>;
|
funcs?: Array<(T) => string | string[]>;
|
||||||
shouldMatchWordsOnly?: boolean;
|
shouldMatchWordsOnly?: boolean;
|
||||||
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
|
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
|
||||||
fuzzy?: boolean;
|
fuzzy?: boolean;
|
||||||
|
@ -69,7 +69,12 @@ export default class QueryMatcher<T extends Object> {
|
||||||
|
|
||||||
if (this._options.funcs) {
|
if (this._options.funcs) {
|
||||||
for (const f of this._options.funcs) {
|
for (const f of this._options.funcs) {
|
||||||
keyValues.push(f(object));
|
const v = f(object);
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
keyValues.push(...v);
|
||||||
|
} else {
|
||||||
|
keyValues.push(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +92,7 @@ export default class QueryMatcher<T extends Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match(query: string): T[] {
|
match(query: string, limit = -1): T[] {
|
||||||
query = this.processQuery(query);
|
query = this.processQuery(query);
|
||||||
if (this._options.shouldMatchWordsOnly) {
|
if (this._options.shouldMatchWordsOnly) {
|
||||||
query = query.replace(/[^\w]/g, '');
|
query = query.replace(/[^\w]/g, '');
|
||||||
|
@ -129,7 +134,10 @@ export default class QueryMatcher<T extends Object> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now map the keys to the result objects. Also remove any duplicates.
|
// Now map the keys to the result objects. Also remove any duplicates.
|
||||||
return uniq(matches.map((match) => match.object));
|
const dedupped = uniq(matches.map((match) => match.object));
|
||||||
|
const maxLength = limit === -1 ? dedupped.length : limit;
|
||||||
|
|
||||||
|
return dedupped.slice(0, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processQuery(query: string): string {
|
private processQuery(query: string): string {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2017, 2018 New Vector Ltd
|
|
||||||
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
Copyright 2017, 2018, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -17,17 +16,19 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
import {uniqBy, sortBy} from "lodash";
|
||||||
import Room from "matrix-js-sdk/src/models/room";
|
import Room from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||||
import QueryMatcher from './QueryMatcher';
|
import QueryMatcher from './QueryMatcher';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import * as sdk from '../index';
|
|
||||||
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
|
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
|
||||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||||
import {uniqBy, sortBy} from "lodash";
|
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||||
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
|
||||||
const ROOM_REGEX = /\B#\S*/g;
|
const ROOM_REGEX = /\B#\S*/g;
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ function matcherObject(room: Room, displayedAlias: string, matchName = "") {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RoomProvider extends AutocompleteProvider {
|
export default class RoomProvider extends AutocompleteProvider {
|
||||||
matcher: QueryMatcher<Room>;
|
protected matcher: QueryMatcher<Room>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(ROOM_REGEX);
|
super(ROOM_REGEX);
|
||||||
|
@ -58,15 +59,28 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
|
protected getRooms() {
|
||||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
const cli = MatrixClientPeg.get();
|
||||||
|
let rooms = cli.getVisibleRooms();
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
if (SettingsStore.getValue("feature_spaces")) {
|
||||||
|
rooms = rooms.filter(r => !r.isSpaceRoom());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCompletions(
|
||||||
|
query: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
let completions = [];
|
let completions = [];
|
||||||
const {command, range} = this.getCurrentCommand(query, selection, force);
|
const {command, range} = this.getCurrentCommand(query, selection, force);
|
||||||
if (command) {
|
if (command) {
|
||||||
// the only reason we need to do this is because Fuse only matches on properties
|
// the only reason we need to do this is because Fuse only matches on properties
|
||||||
let matcherObjects = client.getVisibleRooms().reduce((aliases, room) => {
|
let matcherObjects = this.getRooms().reduce((aliases, room) => {
|
||||||
if (room.getCanonicalAlias()) {
|
if (room.getCanonicalAlias()) {
|
||||||
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
|
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
|
||||||
}
|
}
|
||||||
|
@ -90,7 +104,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
this.matcher.setObjects(matcherObjects);
|
this.matcher.setObjects(matcherObjects);
|
||||||
const matchedString = command[0];
|
const matchedString = command[0];
|
||||||
completions = this.matcher.match(matchedString);
|
completions = this.matcher.match(matchedString, limit);
|
||||||
completions = sortBy(completions, [
|
completions = sortBy(completions, [
|
||||||
(c) => score(matchedString, c.displayedAlias),
|
(c) => score(matchedString, c.displayedAlias),
|
||||||
(c) => c.displayedAlias.length,
|
(c) => c.displayedAlias.length,
|
||||||
|
@ -110,7 +124,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
),
|
),
|
||||||
range,
|
range,
|
||||||
};
|
};
|
||||||
}).filter((completion) => !!completion.completion && completion.completion.length > 0).slice(0, 4);
|
}).filter((completion) => !!completion.completion && completion.completion.length > 0);
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
43
src/autocomplete/SpaceProvider.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 RoomProvider from "./RoomProvider";
|
||||||
|
|
||||||
|
export default class SpaceProvider extends RoomProvider {
|
||||||
|
protected getRooms() {
|
||||||
|
return MatrixClientPeg.get().getVisibleRooms().filter(r => r.isSpaceRoom());
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return _t("Spaces");
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
|
||||||
|
role="listbox"
|
||||||
|
aria-label={_t("Space Autocomplete")}
|
||||||
|
>
|
||||||
|
{ completions }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,7 +102,12 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
this.users = null;
|
this.users = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
async getCompletions(rawQuery: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
|
async getCompletions(
|
||||||
|
rawQuery: string,
|
||||||
|
selection: ISelectionRange,
|
||||||
|
force = false,
|
||||||
|
limit = -1,
|
||||||
|
): Promise<ICompletion[]> {
|
||||||
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
||||||
|
|
||||||
// lazy-load user list into matcher
|
// lazy-load user list into matcher
|
||||||
|
@ -118,7 +123,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
if (fullMatch && fullMatch !== '@') {
|
if (fullMatch && fullMatch !== '@') {
|
||||||
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
||||||
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
|
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
|
||||||
completions = this.matcher.match(query).map((user) => {
|
completions = this.matcher.match(query, limit).map((user) => {
|
||||||
const displayName = (user.name || user.userId || '');
|
const displayName = (user.name || user.userId || '');
|
||||||
return {
|
return {
|
||||||
// Length of completion should equal length of text in decorator. draft-js
|
// Length of completion should equal length of text in decorator. draft-js
|
||||||
|
|
|
@ -222,10 +222,12 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||||
|
// don't let keyboard handling escape the context menu
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
if (!this.props.managed) {
|
if (!this.props.managed) {
|
||||||
if (ev.key === Key.ESCAPE) {
|
if (ev.key === Key.ESCAPE) {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
ev.stopPropagation();
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -258,7 +260,6 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
// consume all other keys in context menu
|
// consume all other keys in context menu
|
||||||
ev.stopPropagation();
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,6 +50,9 @@ class FilePanel extends React.Component {
|
||||||
if (room?.roomId !== this.props?.roomId) return;
|
if (room?.roomId !== this.props?.roomId) return;
|
||||||
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
||||||
|
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
client.decryptEventIfNeeded(ev);
|
||||||
|
|
||||||
if (ev.isBeingDecrypted()) {
|
if (ev.isBeingDecrypted()) {
|
||||||
this.decryptingEvents.add(ev.getId());
|
this.decryptingEvents.add(ev.getId());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -123,12 +123,19 @@ class GroupFilterPanel extends React.Component {
|
||||||
mx_GroupFilterPanel_items_selected: itemsSelected,
|
mx_GroupFilterPanel_items_selected: itemsSelected,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let betaDot;
|
||||||
|
if (SettingsStore.getBetaInfo("feature_spaces") && !localStorage.getItem("mx_seenSpacesBeta")) {
|
||||||
|
betaDot = <div className="mx_BetaDot" />;
|
||||||
|
}
|
||||||
|
|
||||||
let createButton = (
|
let createButton = (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
tooltip
|
tooltip
|
||||||
label={_t("Communities")}
|
label={_t("Communities")}
|
||||||
action="toggle_my_groups"
|
action="toggle_my_groups"
|
||||||
className="mx_TagTile mx_TagTile_plus" />
|
className="mx_TagTile mx_TagTile_plus">
|
||||||
|
{ betaDot }
|
||||||
|
</ActionButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
||||||
|
|
|
@ -110,14 +110,16 @@ class CategoryRoomList extends React.Component {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Failed to add the following room to the group summary',
|
'Failed to add the following room to the group summary',
|
||||||
'', ErrorDialog,
|
'',
|
||||||
|
ErrorDialog,
|
||||||
{
|
{
|
||||||
title: _t(
|
title: _t(
|
||||||
"Failed to add the following rooms to the summary of %(groupId)s:",
|
"Failed to add the following rooms to the summary of %(groupId)s:",
|
||||||
{groupId: this.props.groupId},
|
{groupId: this.props.groupId},
|
||||||
),
|
),
|
||||||
description: errorList.join(", "),
|
description: errorList.join(", "),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
@ -196,7 +198,8 @@ class FeaturedRoom extends React.Component {
|
||||||
{groupId: this.props.groupId},
|
{groupId: this.props.groupId},
|
||||||
),
|
),
|
||||||
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
|
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -289,7 +292,8 @@ class RoleUserList extends React.Component {
|
||||||
{groupId: this.props.groupId},
|
{groupId: this.props.groupId},
|
||||||
),
|
),
|
||||||
description: errorList.join(", "),
|
description: errorList.join(", "),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
@ -352,14 +356,16 @@ class FeaturedUser extends React.Component {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Failed to remove user from community summary',
|
'Failed to remove user from community summary',
|
||||||
'', ErrorDialog,
|
'',
|
||||||
|
ErrorDialog,
|
||||||
{
|
{
|
||||||
title: _t(
|
title: _t(
|
||||||
"Failed to remove a user from the summary of %(groupId)s",
|
"Failed to remove a user from the summary of %(groupId)s",
|
||||||
{groupId: this.props.groupId},
|
{groupId: this.props.groupId},
|
||||||
),
|
),
|
||||||
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
|
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1055,7 +1061,8 @@ export default class GroupView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const membershipButtonClasses = classnames([
|
const membershipButtonClasses = classnames(
|
||||||
|
[
|
||||||
'mx_RoomHeader_textButton',
|
'mx_RoomHeader_textButton',
|
||||||
'mx_GroupView_textButton',
|
'mx_GroupView_textButton',
|
||||||
],
|
],
|
||||||
|
|
|
@ -347,7 +347,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
if (element) {
|
if (element) {
|
||||||
classes = element.classList;
|
classes = element.classList;
|
||||||
}
|
}
|
||||||
} while (element && !cssClasses.some(c => classes.contains(c)));
|
} while (element && (!cssClasses.some(c => classes.contains(c)) || element.offsetParent === null));
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
element.focus();
|
element.focus();
|
||||||
|
@ -416,7 +416,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const roomList = <RoomList
|
const roomList = <RoomList
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
resizeNotifier={null}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import CallMediaHandler from '../../CallMediaHandler';
|
||||||
import { fixupColorFonts } from '../../utils/FontManager';
|
import { fixupColorFonts } from '../../utils/FontManager';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg';
|
import { IMatrixClientCreds } from '../../MatrixClientPeg';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
|
||||||
import TagOrderActions from '../../actions/TagOrderActions';
|
import TagOrderActions from '../../actions/TagOrderActions';
|
||||||
|
@ -59,6 +59,9 @@ import { getKeyBindingsManager, NavigationAction, RoomAction } from '../../KeyBi
|
||||||
import { IOpts } from "../../createRoom";
|
import { IOpts } from "../../createRoom";
|
||||||
import SpacePanel from "../views/spaces/SpacePanel";
|
import SpacePanel from "../views/spaces/SpacePanel";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||||
|
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
|
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
||||||
|
|
||||||
// We need to fetch each pinned message individually (if we don't already have it)
|
// We need to fetch each pinned message individually (if we don't already have it)
|
||||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
|
@ -119,6 +122,7 @@ interface IState {
|
||||||
usageLimitEventContent?: IUsageLimit;
|
usageLimitEventContent?: IUsageLimit;
|
||||||
usageLimitEventTs?: number;
|
usageLimitEventTs?: number;
|
||||||
useCompactLayout: boolean;
|
useCompactLayout: boolean;
|
||||||
|
activeCalls: Array<MatrixCall>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,6 +164,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
// use compact timeline view
|
// use compact timeline view
|
||||||
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
||||||
usageLimitDismissed: false,
|
usageLimitDismissed: false,
|
||||||
|
activeCalls: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// stash the MatrixClient in case we log out before we are unmounted
|
// stash the MatrixClient in case we log out before we are unmounted
|
||||||
|
@ -175,6 +180,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
||||||
|
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
||||||
|
|
||||||
this._updateServerNoticeEvents();
|
this._updateServerNoticeEvents();
|
||||||
|
|
||||||
|
@ -199,6 +205,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
||||||
|
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
||||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
this._matrixClient.removeListener("sync", this.onSync);
|
this._matrixClient.removeListener("sync", this.onSync);
|
||||||
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||||
|
@ -206,15 +213,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
this.resizer.detach();
|
this.resizer.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Child components assume that the client peg will not be null, so give them some
|
private onCallsChanged = () => {
|
||||||
// sort of assurance here by only allowing a re-render if the client is truthy.
|
this.setState({
|
||||||
//
|
activeCalls: CallHandler.sharedInstance().getAllActiveCalls(),
|
||||||
// This is required because `LoggedInView` maintains its own state and if this state
|
});
|
||||||
// updates after the client peg has been made null (during logout), then it will
|
};
|
||||||
// attempt to re-render and the children will throw errors.
|
|
||||||
shouldComponentUpdate() {
|
|
||||||
return Boolean(MatrixClientPeg.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
canResetTimelineInRoom = (roomId) => {
|
canResetTimelineInRoom = (roomId) => {
|
||||||
if (!this._roomView.current) {
|
if (!this._roomView.current) {
|
||||||
|
@ -661,6 +664,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const audioFeedArraysForCalls = this.state.activeCalls.map((call) => {
|
||||||
|
return (
|
||||||
|
<AudioFeedArrayForCall call={call} key={call.callId} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
<div
|
<div
|
||||||
|
@ -685,6 +694,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
<CallContainer />
|
<CallContainer />
|
||||||
<NonUrgentToastContainer />
|
<NonUrgentToastContainer />
|
||||||
<HostSignupContainer />
|
<HostSignupContainer />
|
||||||
|
{audioFeedArraysForCalls}
|
||||||
</MatrixClientContext.Provider>
|
</MatrixClientContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,8 @@ import {RoomUpdateCause} from "../../stores/room-list/models";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
import SecurityCustomisations from "../../customisations/Security";
|
import SecurityCustomisations from "../../customisations/Security";
|
||||||
|
|
||||||
|
import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
// a special initial state which is only used at startup, while we are
|
// a special initial state which is only used at startup, while we are
|
||||||
|
@ -484,42 +486,22 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
startPageChangeTimer() {
|
startPageChangeTimer() {
|
||||||
// Tor doesn't support performance
|
PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
|
||||||
if (!performance || !performance.mark) return null;
|
|
||||||
|
|
||||||
// This shouldn't happen because UNSAFE_componentWillUpdate and componentDidUpdate
|
|
||||||
// are used.
|
|
||||||
if (this.pageChanging) {
|
|
||||||
console.warn('MatrixChat.startPageChangeTimer: timer already started');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.pageChanging = true;
|
|
||||||
performance.mark('element_MatrixChat_page_change_start');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPageChangeTimer() {
|
stopPageChangeTimer() {
|
||||||
// Tor doesn't support performance
|
const perfMonitor = PerformanceMonitor.instance;
|
||||||
if (!performance || !performance.mark) return null;
|
|
||||||
|
|
||||||
if (!this.pageChanging) {
|
perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
|
||||||
console.warn('MatrixChat.stopPageChangeTimer: timer not started');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.pageChanging = false;
|
|
||||||
performance.mark('element_MatrixChat_page_change_stop');
|
|
||||||
performance.measure(
|
|
||||||
'element_MatrixChat_page_change_delta',
|
|
||||||
'element_MatrixChat_page_change_start',
|
|
||||||
'element_MatrixChat_page_change_stop',
|
|
||||||
);
|
|
||||||
performance.clearMarks('element_MatrixChat_page_change_start');
|
|
||||||
performance.clearMarks('element_MatrixChat_page_change_stop');
|
|
||||||
const measurement = performance.getEntriesByName('element_MatrixChat_page_change_delta').pop();
|
|
||||||
|
|
||||||
// In practice, sometimes the entries list is empty, so we get no measurement
|
const entries = perfMonitor.getEntries({
|
||||||
if (!measurement) return null;
|
name: PerformanceEntryNames.PAGE_CHANGE,
|
||||||
|
});
|
||||||
|
const measurement = entries.pop();
|
||||||
|
|
||||||
return measurement.duration;
|
return measurement
|
||||||
|
? measurement.duration
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldTrackPageChange(prevState: IState, state: IState) {
|
shouldTrackPageChange(prevState: IState, state: IState) {
|
||||||
|
@ -740,6 +722,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.showScreenAfterLogin();
|
this.showScreenAfterLogin();
|
||||||
break;
|
break;
|
||||||
case 'toggle_my_groups':
|
case 'toggle_my_groups':
|
||||||
|
// persist that the user has interacted with this, use it to dismiss the beta dot
|
||||||
|
localStorage.setItem("mx_seenSpacesBeta", "1");
|
||||||
// We just dispatch the page change rather than have to worry about
|
// We just dispatch the page change rather than have to worry about
|
||||||
// what the logic is for each of these branches.
|
// what the logic is for each of these branches.
|
||||||
if (this.state.page_type === PageTypes.MyGroups) {
|
if (this.state.page_type === PageTypes.MyGroups) {
|
||||||
|
@ -906,6 +890,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||||
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||||
if (room) {
|
if (room) {
|
||||||
|
// Not all timeline events are decrypted ahead of time anymore
|
||||||
|
// Only the critical ones for a typical UI are
|
||||||
|
// This will start the decryption process for all events when a
|
||||||
|
// user views a room
|
||||||
|
room.decryptAllEvents();
|
||||||
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
||||||
if (theAlias) {
|
if (theAlias) {
|
||||||
presentedId = theAlias;
|
presentedId = theAlias;
|
||||||
|
@ -1094,7 +1083,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
private leaveRoomWarnings(roomId: string) {
|
private leaveRoomWarnings(roomId: string) {
|
||||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
const isSpace = roomToLeave?.isSpaceRoom();
|
const isSpace = SettingsStore.getValue("feature_spaces") && roomToLeave?.isSpaceRoom();
|
||||||
// Show a warning if there are additional complications.
|
// Show a warning if there are additional complications.
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
|
||||||
|
@ -1133,7 +1122,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
const warnings = this.leaveRoomWarnings(roomId);
|
const warnings = this.leaveRoomWarnings(roomId);
|
||||||
|
|
||||||
const isSpace = roomToLeave?.isSpaceRoom();
|
const isSpace = SettingsStore.getValue("feature_spaces") && roomToLeave?.isSpaceRoom();
|
||||||
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
|
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
|
||||||
title: isSpace ? _t("Leave space") : _t("Leave room"),
|
title: isSpace ? _t("Leave space") : _t("Leave room"),
|
||||||
description: (
|
description: (
|
||||||
|
@ -1625,11 +1614,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
action: 'start_registration',
|
action: 'start_registration',
|
||||||
params: params,
|
params: params,
|
||||||
});
|
});
|
||||||
|
PerformanceMonitor.instance.start(PerformanceEntryNames.REGISTER);
|
||||||
} else if (screen === 'login') {
|
} else if (screen === 'login') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'start_login',
|
action: 'start_login',
|
||||||
params: params,
|
params: params,
|
||||||
});
|
});
|
||||||
|
PerformanceMonitor.instance.start(PerformanceEntryNames.LOGIN);
|
||||||
} else if (screen === 'forgot_password') {
|
} else if (screen === 'forgot_password') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'start_password_recovery',
|
action: 'start_password_recovery',
|
||||||
|
@ -1684,6 +1675,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const type = screen === "start_sso" ? "sso" : "cas";
|
const type = screen === "start_sso" ? "sso" : "cas";
|
||||||
PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin());
|
PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin());
|
||||||
} else if (screen === 'groups') {
|
} else if (screen === 'groups') {
|
||||||
|
if (SettingsStore.getValue("feature_spaces")) {
|
||||||
|
dis.dispatch({ action: "view_home_page" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_my_groups',
|
action: 'view_my_groups',
|
||||||
});
|
});
|
||||||
|
@ -1767,6 +1762,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
subAction: params.action,
|
subAction: params.action,
|
||||||
});
|
});
|
||||||
} else if (screen.indexOf('group/') === 0) {
|
} else if (screen.indexOf('group/') === 0) {
|
||||||
|
if (SettingsStore.getValue("feature_spaces")) {
|
||||||
|
dis.dispatch({ action: "view_home_page" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const groupId = screen.substring(6);
|
const groupId = screen.substring(6);
|
||||||
|
|
||||||
// TODO: Check valid group ID
|
// TODO: Check valid group ID
|
||||||
|
@ -1949,6 +1949,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// Create and start the client
|
// Create and start the client
|
||||||
await Lifecycle.setLoggedIn(credentials);
|
await Lifecycle.setLoggedIn(credentials);
|
||||||
await this.postLoginSetup();
|
await this.postLoginSetup();
|
||||||
|
PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN);
|
||||||
|
PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER);
|
||||||
};
|
};
|
||||||
|
|
||||||
// complete security / e2e setup has finished
|
// complete security / e2e setup has finished
|
||||||
|
|
|
@ -34,6 +34,7 @@ import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResiz
|
||||||
import DMRoomMap from "../../utils/DMRoomMap";
|
import DMRoomMap from "../../utils/DMRoomMap";
|
||||||
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import defaultDispatcher from '../../dispatcher/dispatcher';
|
||||||
|
|
||||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||||
const continuedTypes = ['m.sticker', 'm.room.message'];
|
const continuedTypes = ['m.sticker', 'm.room.message'];
|
||||||
|
@ -427,8 +428,10 @@ export default class MessagePanel extends React.Component {
|
||||||
// we get a new DOM node (restarting the animation) when the ghost
|
// we get a new DOM node (restarting the animation) when the ghost
|
||||||
// moves to a different event.
|
// moves to a different event.
|
||||||
return (
|
return (
|
||||||
<li key={"_readuptoghost_"+eventId}
|
<li
|
||||||
className="mx_RoomView_myReadMarker_container">
|
key={"_readuptoghost_"+eventId}
|
||||||
|
className="mx_RoomView_myReadMarker_container"
|
||||||
|
>
|
||||||
{ hr }
|
{ hr }
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -469,6 +472,10 @@ export default class MessagePanel extends React.Component {
|
||||||
return {nextEvent, nextTile};
|
return {nextEvent, nextTile};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _roomHasPendingEdit() {
|
||||||
|
return this.props.room && localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`);
|
||||||
|
}
|
||||||
|
|
||||||
_getEventTiles() {
|
_getEventTiles() {
|
||||||
this.eventNodes = {};
|
this.eventNodes = {};
|
||||||
|
|
||||||
|
@ -542,11 +549,13 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
if (!grouper) {
|
if (!grouper) {
|
||||||
const wantTile = this._shouldShowEvent(mxEv);
|
const wantTile = this._shouldShowEvent(mxEv);
|
||||||
|
const isGrouped = false;
|
||||||
if (wantTile) {
|
if (wantTile) {
|
||||||
// make sure we unpack the array returned by _getTilesForEvent,
|
// make sure we unpack the array returned by _getTilesForEvent,
|
||||||
// otherwise react will auto-generate keys and we will end up
|
// otherwise react will auto-generate keys and we will end up
|
||||||
// replacing all of the DOM elements every time we paginate.
|
// replacing all of the DOM elements every time we paginate.
|
||||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextTile));
|
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, isGrouped,
|
||||||
|
nextEvent, nextTile));
|
||||||
prevEvent = mxEv;
|
prevEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,6 +564,13 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props.editState && this._roomHasPendingEdit) {
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "edit_event",
|
||||||
|
event: this.props.room.findEventById(this._roomHasPendingEdit),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (grouper) {
|
if (grouper) {
|
||||||
ret.push(...grouper.getTiles());
|
ret.push(...grouper.getTiles());
|
||||||
}
|
}
|
||||||
|
@ -562,7 +578,7 @@ export default class MessagePanel extends React.Component {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextEventWithTile) {
|
_getTilesForEvent(prevEvent, mxEv, last, isGrouped=false, nextEvent, nextEventWithTile) {
|
||||||
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
|
@ -570,7 +586,6 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
const isEditing = this.props.editState &&
|
const isEditing = this.props.editState &&
|
||||||
this.props.editState.getEvent().getId() === mxEv.getId();
|
this.props.editState.getEvent().getId() === mxEv.getId();
|
||||||
|
|
||||||
// local echoes have a fake date, which could even be yesterday. Treat them
|
// local echoes have a fake date, which could even be yesterday. Treat them
|
||||||
// as 'today' for the date separators.
|
// as 'today' for the date separators.
|
||||||
let ts1 = mxEv.getTs();
|
let ts1 = mxEv.getTs();
|
||||||
|
@ -582,7 +597,7 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
// do we need a date separator since the last event?
|
// do we need a date separator since the last event?
|
||||||
const wantsDateSeparator = this._wantsDateSeparator(prevEvent, eventDate);
|
const wantsDateSeparator = this._wantsDateSeparator(prevEvent, eventDate);
|
||||||
if (wantsDateSeparator) {
|
if (wantsDateSeparator && !isGrouped) {
|
||||||
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
|
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
@ -966,9 +981,9 @@ class CreationGrouper {
|
||||||
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||||
|
|
||||||
const panel = this.panel;
|
const panel = this.panel;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
const isGrouped = true;
|
||||||
const createEvent = this.createEvent;
|
const createEvent = this.createEvent;
|
||||||
const lastShownEvent = this.lastShownEvent;
|
const lastShownEvent = this.lastShownEvent;
|
||||||
|
|
||||||
|
@ -982,12 +997,12 @@ class CreationGrouper {
|
||||||
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
||||||
if (panel._shouldShowEvent(createEvent)) {
|
if (panel._shouldShowEvent(createEvent)) {
|
||||||
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
|
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
|
||||||
ret.push(...panel._getTilesForEvent(createEvent, createEvent, false));
|
ret.push(...panel._getTilesForEvent(createEvent, createEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const ejected of this.ejectedEvents) {
|
for (const ejected of this.ejectedEvents) {
|
||||||
ret.push(...panel._getTilesForEvent(
|
ret.push(...panel._getTilesForEvent(
|
||||||
createEvent, ejected, createEvent === lastShownEvent,
|
createEvent, ejected, createEvent === lastShownEvent, isGrouped,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -996,7 +1011,7 @@ class CreationGrouper {
|
||||||
// of EventListSummary, render each member event as if the previous
|
// of EventListSummary, render each member event as if the previous
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
// timestamp of the current event, and no DateSeparator is inserted.
|
// timestamp of the current event, and no DateSeparator is inserted.
|
||||||
return panel._getTilesForEvent(e, e, e === lastShownEvent);
|
return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
|
||||||
}).reduce((a, b) => a.concat(b), []);
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
|
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
|
||||||
const ev = this.events[this.events.length - 1];
|
const ev = this.events[this.events.length - 1];
|
||||||
|
@ -1081,7 +1096,7 @@ class RedactionGrouper {
|
||||||
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||||
|
const isGrouped = true;
|
||||||
const panel = this.panel;
|
const panel = this.panel;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
const lastShownEvent = this.lastShownEvent;
|
const lastShownEvent = this.lastShownEvent;
|
||||||
|
@ -1101,7 +1116,8 @@ class RedactionGrouper {
|
||||||
let eventTiles = this.events.map((e, i) => {
|
let eventTiles = this.events.map((e, i) => {
|
||||||
senders.add(e.sender);
|
senders.add(e.sender);
|
||||||
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
|
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
|
||||||
return panel._getTilesForEvent(prevEvent, e, e === lastShownEvent, this.nextEvent, this.nextEventTile);
|
return panel._getTilesForEvent(
|
||||||
|
prevEvent, e, e === lastShownEvent, isGrouped, this.nextEvent, this.nextEventTile);
|
||||||
}).reduce((a, b) => a.concat(b), []);
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
|
|
||||||
if (eventTiles.length === 0) {
|
if (eventTiles.length === 0) {
|
||||||
|
@ -1180,7 +1196,7 @@ class MemberGrouper {
|
||||||
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||||
|
const isGrouped = true;
|
||||||
const panel = this.panel;
|
const panel = this.panel;
|
||||||
const lastShownEvent = this.lastShownEvent;
|
const lastShownEvent = this.lastShownEvent;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
@ -1213,7 +1229,7 @@ class MemberGrouper {
|
||||||
// of MemberEventListSummary, render each member event as if the previous
|
// of MemberEventListSummary, render each member event as if the previous
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
// timestamp of the current event, and no DateSeparator is inserted.
|
// timestamp of the current event, and no DateSeparator is inserted.
|
||||||
return panel._getTilesForEvent(e, e, e === lastShownEvent);
|
return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
|
||||||
}).reduce((a, b) => a.concat(b), []);
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
|
|
||||||
if (eventTiles.length === 0) {
|
if (eventTiles.length === 0) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import BetaCard from "../views/beta/BetaCard";
|
||||||
|
|
||||||
@replaceableComponent("structures.MyGroups")
|
@replaceableComponent("structures.MyGroups")
|
||||||
export default class MyGroups extends React.Component {
|
export default class MyGroups extends React.Component {
|
||||||
|
@ -139,6 +140,7 @@ export default class MyGroups extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>*/}
|
</div>*/}
|
||||||
</div>
|
</div>
|
||||||
|
<BetaCard featureId="feature_spaces" title={_t("Communities are changing to Spaces")} />
|
||||||
<div className="mx_MyGroups_content">
|
<div className="mx_MyGroups_content">
|
||||||
{ contentHeader }
|
{ contentHeader }
|
||||||
{ content }
|
{ content }
|
||||||
|
|
|
@ -35,6 +35,7 @@ import {Action} from "../../dispatcher/actions";
|
||||||
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||||
import WidgetCard from "../views/right_panel/WidgetCard";
|
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
|
||||||
@replaceableComponent("structures.RightPanel")
|
@replaceableComponent("structures.RightPanel")
|
||||||
export default class RightPanel extends React.Component {
|
export default class RightPanel extends React.Component {
|
||||||
|
@ -85,7 +86,9 @@ export default class RightPanel extends React.Component {
|
||||||
return RightPanelPhases.GroupMemberList;
|
return RightPanelPhases.GroupMemberList;
|
||||||
}
|
}
|
||||||
return rps.groupPanelPhase;
|
return rps.groupPanelPhase;
|
||||||
} else if (this.props.room?.isSpaceRoom() && !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase)) {
|
} else if (SettingsStore.getValue("feature_spaces") && this.props.room?.isSpaceRoom()
|
||||||
|
&& !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase)
|
||||||
|
) {
|
||||||
return RightPanelPhases.SpaceMemberList;
|
return RightPanelPhases.SpaceMemberList;
|
||||||
} else if (userForPanel) {
|
} else if (userForPanel) {
|
||||||
// XXX FIXME AAAAAARGH: What is going on with this class!? It takes some of its state
|
// XXX FIXME AAAAAARGH: What is going on with this class!? It takes some of its state
|
||||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { createRef } from "react";
|
import { createRef } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import { ActionPayload } from "../../dispatcher/payloads";
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
|
@ -26,7 +28,7 @@ import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||||
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
|
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
|
import SpaceStore, { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/SpaceStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -40,6 +42,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
query: string;
|
query: string;
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
|
inSpaces: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.RoomSearch")
|
@replaceableComponent("structures.RoomSearch")
|
||||||
|
@ -54,11 +57,13 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
query: "",
|
query: "",
|
||||||
focused: false,
|
focused: false,
|
||||||
|
inSpaces: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
// clear filter when changing spaces, in future we may wish to maintain a filter per-space
|
// clear filter when changing spaces, in future we may wish to maintain a filter per-space
|
||||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput);
|
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput);
|
||||||
|
SpaceStore.instance.on(UPDATE_TOP_LEVEL_SPACES, this.onSpaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
|
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
|
||||||
|
@ -79,8 +84,15 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput);
|
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput);
|
||||||
|
SpaceStore.instance.off(UPDATE_TOP_LEVEL_SPACES, this.onSpaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onSpaces = (spaces: Room[]) => {
|
||||||
|
this.setState({
|
||||||
|
inSpaces: spaces.length > 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
private onAction = (payload: ActionPayload) => {
|
||||||
if (payload.action === 'view_room' && payload.clear_search) {
|
if (payload.action === 'view_room' && payload.clear_search) {
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
|
@ -152,6 +164,11 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused,
|
'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let placeholder = _t("Filter");
|
||||||
|
if (this.state.inSpaces) {
|
||||||
|
placeholder = _t("Filter all spaces");
|
||||||
|
}
|
||||||
|
|
||||||
let icon = (
|
let icon = (
|
||||||
<div className='mx_RoomSearch_icon' />
|
<div className='mx_RoomSearch_icon' />
|
||||||
);
|
);
|
||||||
|
@ -165,7 +182,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
placeholder={_t("Filter")}
|
placeholder={placeholder}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -200,7 +200,8 @@ export default class RoomStatusBar extends React.Component {
|
||||||
} else if (resourceLimitError) {
|
} else if (resourceLimitError) {
|
||||||
title = messageForResourceLimitError(
|
title = messageForResourceLimitError(
|
||||||
resourceLimitError.data.limit_type,
|
resourceLimitError.data.limit_type,
|
||||||
resourceLimitError.data.admin_contact, {
|
resourceLimitError.data.admin_contact,
|
||||||
|
{
|
||||||
'monthly_active_user': _td(
|
'monthly_active_user': _td(
|
||||||
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
|
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
|
||||||
"Please <a>contact your service administrator</a> to continue using the service.",
|
"Please <a>contact your service administrator</a> to continue using the service.",
|
||||||
|
@ -213,7 +214,8 @@ export default class RoomStatusBar extends React.Component {
|
||||||
"Your message wasn't sent because this homeserver has exceeded a resource limit. " +
|
"Your message wasn't sent because this homeserver has exceeded a resource limit. " +
|
||||||
"Please <a>contact your service administrator</a> to continue using the service.",
|
"Please <a>contact your service administrator</a> to continue using the service.",
|
||||||
),
|
),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
title = _t('Some of your messages have not been sent');
|
title = _t('Some of your messages have not been sent');
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,6 +190,9 @@ export interface IState {
|
||||||
rejectError?: Error;
|
rejectError?: Error;
|
||||||
hasPinnedWidgets?: boolean;
|
hasPinnedWidgets?: boolean;
|
||||||
dragCounter: number;
|
dragCounter: number;
|
||||||
|
// whether or not a spaces context switch brought us here,
|
||||||
|
// if it did we don't want the room to be marked as read as soon as it is loaded.
|
||||||
|
wasContextSwitch?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.RoomView")
|
@replaceableComponent("structures.RoomView")
|
||||||
|
@ -326,6 +329,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
|
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
|
||||||
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
|
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
|
||||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
||||||
|
wasContextSwitch: RoomViewStore.getWasContextSwitch(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
|
if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
|
||||||
|
@ -807,7 +811,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onEvent = (ev) => {
|
private onEvent = (ev) => {
|
||||||
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return;
|
if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || ev.shouldAttemptDecryption()) return;
|
||||||
this.handleEffects(ev);
|
this.handleEffects(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1594,33 +1598,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
|
this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onFullscreenClick = () => {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'video_fullscreen',
|
|
||||||
fullscreen: true,
|
|
||||||
}, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
private onMuteAudioClick = () => {
|
|
||||||
const call = this.getCallForRoom();
|
|
||||||
if (!call) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newState = !call.isMicrophoneMuted();
|
|
||||||
call.setMicrophoneMuted(newState);
|
|
||||||
this.forceUpdate(); // TODO: just update the voip buttons
|
|
||||||
};
|
|
||||||
|
|
||||||
private onMuteVideoClick = () => {
|
|
||||||
const call = this.getCallForRoom();
|
|
||||||
if (!call) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newState = !call.isLocalVideoMuted();
|
|
||||||
call.setLocalVideoMuted(newState);
|
|
||||||
this.forceUpdate(); // TODO: just update the voip buttons
|
|
||||||
};
|
|
||||||
|
|
||||||
private onStatusBarVisible = () => {
|
private onStatusBarVisible = () => {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1636,24 +1613,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* called by the parent component when PageUp/Down/etc is pressed.
|
|
||||||
*
|
|
||||||
* We pass it down to the scroll panel.
|
|
||||||
*/
|
|
||||||
private handleScrollKey = ev => {
|
|
||||||
let panel;
|
|
||||||
if (this.searchResultsPanel.current) {
|
|
||||||
panel = this.searchResultsPanel.current;
|
|
||||||
} else if (this.messagePanel) {
|
|
||||||
panel = this.messagePanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel) {
|
|
||||||
panel.handleScrollKey(ev);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get any current call for this room
|
* get any current call for this room
|
||||||
*/
|
*/
|
||||||
|
@ -1746,7 +1705,10 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const myMembership = this.state.room.getMyMembership();
|
const myMembership = this.state.room.getMyMembership();
|
||||||
if (myMembership === "invite" && !this.state.room.isSpaceRoom()) { // SpaceRoomView handles invites itself
|
if (myMembership === "invite"
|
||||||
|
// SpaceRoomView handles invites itself
|
||||||
|
&& (!SettingsStore.getValue("feature_spaces") || !this.state.room.isSpaceRoom())
|
||||||
|
) {
|
||||||
if (this.state.joining || this.state.rejecting) {
|
if (this.state.joining || this.state.rejecting) {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
@ -1888,7 +1850,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (!this.state.canPeek && !this.state.room?.isSpaceRoom()) {
|
if (!this.state.canPeek && (!SettingsStore.getValue("feature_spaces") || !this.state.room?.isSpaceRoom())) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
{ previewBar }
|
{ previewBar }
|
||||||
|
@ -1910,7 +1872,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SettingsStore.getValue("feature_spaces") && this.state.room?.isSpaceRoom()) {
|
if (this.state.room?.isSpaceRoom()) {
|
||||||
return <SpaceRoomView
|
return <SpaceRoomView
|
||||||
space={this.state.room}
|
space={this.state.room}
|
||||||
justCreatedOpts={this.props.justCreatedOpts}
|
justCreatedOpts={this.props.justCreatedOpts}
|
||||||
|
@ -2014,6 +1976,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
timelineSet={this.state.room.getUnfilteredTimelineSet()}
|
timelineSet={this.state.room.getUnfilteredTimelineSet()}
|
||||||
showReadReceipts={this.state.showReadReceipts}
|
showReadReceipts={this.state.showReadReceipts}
|
||||||
manageReadReceipts={!this.state.isPeeking}
|
manageReadReceipts={!this.state.isPeeking}
|
||||||
|
sendReadReceiptOnLoad={!this.state.wasContextSwitch}
|
||||||
manageReadMarkers={!this.state.isPeeking}
|
manageReadMarkers={!this.state.isPeeking}
|
||||||
hidden={hideMessagePanel}
|
hidden={hideMessagePanel}
|
||||||
highlightedEventId={highlightedEventId}
|
highlightedEventId={highlightedEventId}
|
||||||
|
|
|
@ -884,9 +884,13 @@ export default class ScrollPanel extends React.Component {
|
||||||
|
|
||||||
// give the <ol> an explicit role=list because Safari+VoiceOver seems to think an ordered-list with
|
// give the <ol> an explicit role=list because Safari+VoiceOver seems to think an ordered-list with
|
||||||
// list-style-type: none; is no longer a list
|
// list-style-type: none; is no longer a list
|
||||||
return (<AutoHideScrollbar wrappedRef={this._collectScroll}
|
return (
|
||||||
|
<AutoHideScrollbar
|
||||||
|
wrappedRef={this._collectScroll}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
|
className={`mx_ScrollPanel ${this.props.className}`}
|
||||||
|
style={this.props.style}
|
||||||
|
>
|
||||||
{ this.props.fixedChildren }
|
{ this.props.fixedChildren }
|
||||||
<div className="mx_RoomView_messageListWrapper">
|
<div className="mx_RoomView_messageListWrapper">
|
||||||
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useMemo, useState} from "react";
|
import React, {ReactNode, useMemo, useState} from "react";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
@ -24,7 +24,7 @@ import {sortBy} from "lodash";
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
|
||||||
import BaseDialog from "../views/dialogs/BaseDialog";
|
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||||
import Spinner from "../views/elements/Spinner";
|
import Spinner from "../views/elements/Spinner";
|
||||||
import SearchBox from "./SearchBox";
|
import SearchBox from "./SearchBox";
|
||||||
|
@ -39,11 +39,15 @@ import {mediaFromMxc} from "../../customisations/Media";
|
||||||
import InfoTooltip from "../views/elements/InfoTooltip";
|
import InfoTooltip from "../views/elements/InfoTooltip";
|
||||||
import TextWithTooltip from "../views/elements/TextWithTooltip";
|
import TextWithTooltip from "../views/elements/TextWithTooltip";
|
||||||
import {useStateToggle} from "../../hooks/useStateToggle";
|
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
|
import {getOrder} from "../../stores/SpaceStore";
|
||||||
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
|
import {linkifyElement} from "../../HtmlUtils";
|
||||||
|
|
||||||
interface IHierarchyProps {
|
interface IHierarchyProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
refreshToken?: any;
|
refreshToken?: any;
|
||||||
|
additionalButtons?: ReactNode;
|
||||||
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
|
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +76,7 @@ export interface ISpaceSummaryEvent {
|
||||||
order?: string;
|
order?: string;
|
||||||
suggested?: boolean;
|
suggested?: boolean;
|
||||||
auto_join?: boolean;
|
auto_join?: boolean;
|
||||||
via?: string;
|
via?: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
@ -106,8 +110,16 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
const cliRoom = cli.getRoom(room.room_id);
|
const cliRoom = cli.getRoom(room.room_id);
|
||||||
const myMembership = cliRoom?.getMyMembership();
|
const myMembership = cliRoom?.getMyMembership();
|
||||||
|
|
||||||
const onPreviewClick = () => onViewRoomClick(false);
|
const onPreviewClick = (ev: ButtonEvent) => {
|
||||||
const onJoinClick = () => onViewRoomClick(true);
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
onViewRoomClick(false);
|
||||||
|
}
|
||||||
|
const onJoinClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
onViewRoomClick(true);
|
||||||
|
}
|
||||||
|
|
||||||
let button;
|
let button;
|
||||||
if (myMembership === "join") {
|
if (myMembership === "join") {
|
||||||
|
@ -140,7 +152,7 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
let description = _t("%(count)s members", { count: room.num_joined_members });
|
let description = _t("%(count)s members", { count: room.num_joined_members });
|
||||||
if (numChildRooms) {
|
if (numChildRooms !== undefined) {
|
||||||
description += " · " + _t("%(count)s rooms", { count: numChildRooms });
|
description += " · " + _t("%(count)s rooms", { count: numChildRooms });
|
||||||
}
|
}
|
||||||
if (room.topic) {
|
if (room.topic) {
|
||||||
|
@ -161,7 +173,16 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
{ suggestedSection }
|
{ suggestedSection }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomDirectory_roomTile_info">
|
<div
|
||||||
|
className="mx_SpaceRoomDirectory_roomTile_info"
|
||||||
|
ref={e => e && linkifyElement(e)}
|
||||||
|
onClick={ev => {
|
||||||
|
// prevent clicks on links from bubbling up to the room tile
|
||||||
|
if ((ev.target as HTMLElement).tagName === "A") {
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{ description }
|
{ description }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_SpaceRoomDirectory_actions">
|
<div className="mx_SpaceRoomDirectory_actions">
|
||||||
|
@ -254,7 +275,11 @@ export const HierarchyLevel = ({
|
||||||
const space = cli.getRoom(spaceId);
|
const space = cli.getRoom(spaceId);
|
||||||
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
||||||
|
|
||||||
const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
|
const children = Array.from(relations.get(spaceId)?.values() || []);
|
||||||
|
const sortedChildren = sortBy(children, ev => {
|
||||||
|
// XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
|
||||||
|
return getOrder(ev.content.order, null, ev.state_key);
|
||||||
|
});
|
||||||
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
|
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
|
||||||
const roomId = ev.state_key;
|
const roomId = ev.state_key;
|
||||||
if (!rooms.has(roomId)) return result;
|
if (!rooms.has(roomId)) return result;
|
||||||
|
@ -331,9 +356,9 @@ export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: a
|
||||||
parentChildRelations.getOrCreate(ev.room_id, new Map()).set(ev.state_key, ev);
|
parentChildRelations.getOrCreate(ev.room_id, new Map()).set(ev.state_key, ev);
|
||||||
childParentRelations.getOrCreate(ev.state_key, new Set()).add(ev.room_id);
|
childParentRelations.getOrCreate(ev.state_key, new Set()).add(ev.room_id);
|
||||||
}
|
}
|
||||||
if (Array.isArray(ev.content["via"])) {
|
if (Array.isArray(ev.content.via)) {
|
||||||
const set = viaMap.getOrCreate(ev.state_key, new Set());
|
const set = viaMap.getOrCreate(ev.state_key, new Set());
|
||||||
ev.content["via"].forEach(via => set.add(via));
|
ev.content.via.forEach(via => set.add(via));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -350,6 +375,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
initialText = "",
|
initialText = "",
|
||||||
showRoom,
|
showRoom,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
|
additionalButtons,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -415,29 +441,42 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces });
|
countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces });
|
||||||
}
|
}
|
||||||
|
|
||||||
let editSection;
|
let manageButtons;
|
||||||
if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
||||||
const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
|
const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
|
||||||
return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][];
|
return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][];
|
||||||
});
|
});
|
||||||
|
|
||||||
let buttons;
|
|
||||||
if (selectedRelations.length) {
|
|
||||||
const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
|
const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
|
||||||
return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
|
return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
|
||||||
});
|
});
|
||||||
|
|
||||||
const disabled = removing || saving;
|
const disabled = !selectedRelations.length || removing || saving;
|
||||||
|
|
||||||
buttons = <>
|
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
|
||||||
<AccessibleButton
|
let props = {};
|
||||||
|
if (!selectedRelations.length) {
|
||||||
|
Button = AccessibleTooltipButton;
|
||||||
|
props = {
|
||||||
|
tooltip: _t("Select a room below first"),
|
||||||
|
yOffset: -40,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
manageButtons = <>
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setRemoving(true);
|
setRemoving(true);
|
||||||
try {
|
try {
|
||||||
for (const [parentId, childId] of selectedRelations) {
|
for (const [parentId, childId] of selectedRelations) {
|
||||||
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
|
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
|
||||||
parentChildMap.get(parentId).get(childId).content = {};
|
parentChildMap.get(parentId).delete(childId);
|
||||||
|
if (parentChildMap.get(parentId).size > 0) {
|
||||||
parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
|
parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
|
||||||
|
} else {
|
||||||
|
parentChildMap.delete(parentId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(_t("Failed to remove some rooms. Try again later"));
|
setError(_t("Failed to remove some rooms. Try again later"));
|
||||||
|
@ -448,8 +487,9 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{ removing ? _t("Removing...") : _t("Remove") }
|
{ removing ? _t("Removing...") : _t("Remove") }
|
||||||
</AccessibleButton>
|
</Button>
|
||||||
<AccessibleButton
|
<Button
|
||||||
|
{...props}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
|
@ -480,15 +520,10 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
? _t("Saving...")
|
? _t("Saving...")
|
||||||
: (selectionAllSuggested ? _t("Mark as not suggested") : _t("Mark as suggested"))
|
: (selectionAllSuggested ? _t("Mark as not suggested") : _t("Mark as suggested"))
|
||||||
}
|
}
|
||||||
</AccessibleButton>
|
</Button>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
editSection = <span>
|
|
||||||
{ buttons }
|
|
||||||
</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let results;
|
let results;
|
||||||
if (roomsMap.size) {
|
if (roomsMap.size) {
|
||||||
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
||||||
|
@ -532,7 +567,10 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
content = <>
|
content = <>
|
||||||
<div className="mx_SpaceRoomDirectory_listHeader">
|
<div className="mx_SpaceRoomDirectory_listHeader">
|
||||||
{ countsStr }
|
{ countsStr }
|
||||||
{ editSection }
|
<span>
|
||||||
|
{ additionalButtons }
|
||||||
|
{ manageButtons }
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{ error && <div className="mx_SpaceRoomDirectory_error">
|
{ error && <div className="mx_SpaceRoomDirectory_error">
|
||||||
{ error }
|
{ error }
|
||||||
|
@ -550,7 +588,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
return <>
|
return <>
|
||||||
<SearchBox
|
<SearchBox
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
placeholder={ _t("Search names and description") }
|
placeholder={ _t("Search names and descriptions") }
|
||||||
onSearch={setQuery}
|
onSearch={setQuery}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
initialValue={initialText}
|
initialValue={initialText}
|
||||||
|
|
|
@ -52,8 +52,19 @@ import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
import SpaceStore from "../../stores/SpaceStore";
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
import FacePile from "../views/elements/FacePile";
|
import FacePile from "../views/elements/FacePile";
|
||||||
import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
|
import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
|
||||||
import {allSettled} from "../../utils/promise";
|
import {ChevronFace, ContextMenuButton, useContextMenu} from "./ContextMenu";
|
||||||
import {calculateRoomVia} from "../../utils/permalinks/Permalinks";
|
import IconizedContextMenu, {
|
||||||
|
IconizedContextMenuOption,
|
||||||
|
IconizedContextMenuOptionList,
|
||||||
|
} from "../views/context_menus/IconizedContextMenu";
|
||||||
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
|
import {BetaPill} from "../views/beta/BetaCard";
|
||||||
|
import {USER_LABS_TAB} from "../views/dialogs/UserSettingsDialog";
|
||||||
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
import Modal from "../../Modal";
|
||||||
|
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
|
||||||
|
import SdkConfig from "../../SdkConfig";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -65,6 +76,7 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
|
createdRooms?: boolean; // internal state for the creation wizard
|
||||||
showRightPanel: boolean;
|
showRightPanel: boolean;
|
||||||
myMembership: string;
|
myMembership: string;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +91,26 @@ enum Phase {
|
||||||
PrivateExistingRooms,
|
PrivateExistingRooms,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: Temporary for the Spaces Beta only
|
||||||
|
export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
||||||
|
if (!SdkConfig.get().bug_report_endpoint_url) return null;
|
||||||
|
|
||||||
|
return <div className="mx_SpaceFeedbackPrompt">
|
||||||
|
<hr />
|
||||||
|
<div>
|
||||||
|
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span>
|
||||||
|
<AccessibleButton kind="link" onClick={() => {
|
||||||
|
if (onClick) onClick();
|
||||||
|
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
||||||
|
featureId: "feature_spaces",
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
{ _t("Feedback") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
const RoomMemberCount = ({ room, children }) => {
|
const RoomMemberCount = ({ room, children }) => {
|
||||||
const members = useRoomMembers(room);
|
const members = useRoomMembers(room);
|
||||||
const count = members.length;
|
const count = members.length;
|
||||||
|
@ -130,15 +162,39 @@ const SpaceInfo = ({ space }) => {
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onBetaClick = () => {
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: Action.ViewUserSettings,
|
||||||
|
initialTabId: USER_LABS_TAB,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const myMembership = useMyRoomMembership(space);
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
const spacesEnabled = SettingsStore.getValue("feature_spaces");
|
||||||
|
|
||||||
let inviterSection;
|
let inviterSection;
|
||||||
let joinButtons;
|
let joinButtons;
|
||||||
if (myMembership === "invite") {
|
if (myMembership === "join") {
|
||||||
|
// XXX remove this when spaces leaves Beta
|
||||||
|
joinButtons = (
|
||||||
|
<AccessibleButton
|
||||||
|
kind="danger_outline"
|
||||||
|
onClick={() => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "leave_room",
|
||||||
|
room_id: space.roomId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Leave") }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
} else if (myMembership === "invite") {
|
||||||
const inviteSender = space.getMember(cli.getUserId())?.events.member?.getSender();
|
const inviteSender = space.getMember(cli.getUserId())?.events.member?.getSender();
|
||||||
const inviter = inviteSender && space.getMember(inviteSender);
|
const inviter = inviteSender && space.getMember(inviteSender);
|
||||||
|
|
||||||
|
@ -174,6 +230,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
onJoinButtonClicked();
|
onJoinButtonClicked();
|
||||||
}}
|
}}
|
||||||
|
disabled={!spacesEnabled}
|
||||||
>
|
>
|
||||||
{ _t("Accept") }
|
{ _t("Accept") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -186,10 +243,11 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
onJoinButtonClicked();
|
onJoinButtonClicked();
|
||||||
}}
|
}}
|
||||||
|
disabled={!spacesEnabled}
|
||||||
>
|
>
|
||||||
{ _t("Join") }
|
{ _t("Join") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (busy) {
|
if (busy) {
|
||||||
|
@ -197,6 +255,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_preview">
|
return <div className="mx_SpaceRoomView_preview">
|
||||||
|
<BetaPill onClick={onBetaClick} />
|
||||||
{ inviterSection }
|
{ inviterSection }
|
||||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
<h1 className="mx_SpaceRoomView_preview_name">
|
<h1 className="mx_SpaceRoomView_preview_name">
|
||||||
|
@ -214,9 +273,84 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
<div className="mx_SpaceRoomView_preview_joinButtons">
|
<div className="mx_SpaceRoomView_preview_joinButtons">
|
||||||
{ joinButtons }
|
{ joinButtons }
|
||||||
</div>
|
</div>
|
||||||
|
{ !spacesEnabled && <div className="mx_SpaceRoomView_preview_spaceBetaPrompt">
|
||||||
|
{ myMembership === "join"
|
||||||
|
? _t("To view %(spaceName)s, turn on the <a>Spaces beta</a>", {
|
||||||
|
spaceName: space.name,
|
||||||
|
}, {
|
||||||
|
a: sub => <AccessibleButton onClick={onBetaClick} kind="link">{ sub }</AccessibleButton>,
|
||||||
|
})
|
||||||
|
: _t("To join %(spaceName)s, turn on the <a>Spaces beta</a>", {
|
||||||
|
spaceName: space.name,
|
||||||
|
}, {
|
||||||
|
a: sub => <AccessibleButton onClick={onBetaClick} kind="link">{ sub }</AccessibleButton>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div> }
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
||||||
|
|
||||||
|
let contextMenu;
|
||||||
|
if (menuDisplayed) {
|
||||||
|
const rect = handle.current.getBoundingClientRect();
|
||||||
|
contextMenu = <IconizedContextMenu
|
||||||
|
left={rect.left + window.pageXOffset + 0}
|
||||||
|
top={rect.bottom + window.pageYOffset + 8}
|
||||||
|
chevronFace={ChevronFace.None}
|
||||||
|
onFinished={closeMenu}
|
||||||
|
className="mx_RoomTile_contextMenu"
|
||||||
|
compact
|
||||||
|
>
|
||||||
|
<IconizedContextMenuOptionList first>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
label={_t("Create new room")}
|
||||||
|
iconClassName="mx_RoomList_iconPlus"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closeMenu();
|
||||||
|
|
||||||
|
if (await showCreateNewRoom(cli, space)) {
|
||||||
|
onNewRoomAdded();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
label={_t("Add existing room")}
|
||||||
|
iconClassName="mx_RoomList_iconHash"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closeMenu();
|
||||||
|
|
||||||
|
const [added] = await showAddExistingRooms(cli, space);
|
||||||
|
if (added) {
|
||||||
|
onNewRoomAdded();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconizedContextMenuOptionList>
|
||||||
|
</IconizedContextMenu>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<ContextMenuButton
|
||||||
|
kind="primary"
|
||||||
|
inputRef={handle}
|
||||||
|
onClick={openMenu}
|
||||||
|
isExpanded={menuDisplayed}
|
||||||
|
label={_t("Add")}
|
||||||
|
>
|
||||||
|
{ _t("Add") }
|
||||||
|
</ContextMenuButton>
|
||||||
|
{ contextMenu }
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
const SpaceLanding = ({ space }) => {
|
const SpaceLanding = ({ space }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const myMembership = useMyRoomMembership(space);
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
@ -241,32 +375,20 @@ const SpaceLanding = ({ space }) => {
|
||||||
|
|
||||||
const [refreshToken, forceUpdate] = useStateToggle(false);
|
const [refreshToken, forceUpdate] = useStateToggle(false);
|
||||||
|
|
||||||
let addRoomButtons;
|
let addRoomButton;
|
||||||
if (canAddRooms) {
|
if (canAddRooms) {
|
||||||
addRoomButtons = <React.Fragment>
|
addRoomButton = <SpaceLandingAddButton space={space} onNewRoomAdded={forceUpdate} />;
|
||||||
<AccessibleButton className="mx_SpaceRoomView_landing_addButton" onClick={async () => {
|
|
||||||
const [added] = await showAddExistingRooms(cli, space);
|
|
||||||
if (added) {
|
|
||||||
forceUpdate();
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{ _t("Add existing rooms & spaces") }
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton className="mx_SpaceRoomView_landing_createButton" onClick={() => {
|
|
||||||
showCreateNewRoom(cli, space);
|
|
||||||
}}>
|
|
||||||
{ _t("Create a new room") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</React.Fragment>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let settingsButton;
|
let settingsButton;
|
||||||
if (shouldShowSpaceSettings(cli, space)) {
|
if (shouldShowSpaceSettings(cli, space)) {
|
||||||
settingsButton = <AccessibleButton className="mx_SpaceRoomView_landing_settingsButton" onClick={() => {
|
settingsButton = <AccessibleTooltipButton
|
||||||
|
className="mx_SpaceRoomView_landing_settingsButton"
|
||||||
|
onClick={() => {
|
||||||
showSpaceSettings(cli, space);
|
showSpaceSettings(cli, space);
|
||||||
}}>
|
}}
|
||||||
{ _t("Settings") }
|
title={_t("Settings")}
|
||||||
</AccessibleButton>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMembersClick = () => {
|
const onMembersClick = () => {
|
||||||
|
@ -293,17 +415,20 @@ const SpaceLanding = ({ space }) => {
|
||||||
<SpaceInfo space={space} />
|
<SpaceInfo space={space} />
|
||||||
<FacePile room={space} onlyKnownUsers={false} numShown={7} onClick={onMembersClick} />
|
<FacePile room={space} onlyKnownUsers={false} numShown={7} onClick={onMembersClick} />
|
||||||
{ inviteButton }
|
{ inviteButton }
|
||||||
|
{ settingsButton }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_SpaceRoomView_landing_topic">
|
<div className="mx_SpaceRoomView_landing_topic">
|
||||||
<RoomTopic room={space} />
|
<RoomTopic room={space} />
|
||||||
</div>
|
</div>
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
<hr />
|
<hr />
|
||||||
<div className="mx_SpaceRoomView_landing_adminButtons">
|
|
||||||
{ addRoomButtons }
|
|
||||||
{ settingsButton }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SpaceHierarchy space={space} showRoom={showRoom} refreshToken={refreshToken} />
|
<SpaceHierarchy
|
||||||
|
space={space}
|
||||||
|
showRoom={showRoom}
|
||||||
|
refreshToken={refreshToken}
|
||||||
|
additionalButtons={addRoomButton}
|
||||||
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -325,14 +450,18 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
value={roomNames[i]}
|
value={roomNames[i]}
|
||||||
onChange={ev => setRoomName(i, ev.target.value)}
|
onChange={ev => setRoomName(i, ev.target.value)}
|
||||||
autoFocus={i === 2}
|
autoFocus={i === 2}
|
||||||
|
disabled={busy}
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNextClick = async () => {
|
const onNextClick = async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
setError("");
|
setError("");
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
await Promise.all(roomNames.map(name => name.trim()).filter(Boolean).map(name => {
|
const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean);
|
||||||
|
await Promise.all(filteredRoomNames.map(name => {
|
||||||
return createRoom({
|
return createRoom({
|
||||||
createOpts: {
|
createOpts: {
|
||||||
preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat,
|
preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat,
|
||||||
|
@ -345,7 +474,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
parentSpace: space,
|
parentSpace: space,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
onFinished();
|
onFinished(filteredRoomNames.length > 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to create initial space rooms", e);
|
console.error("Failed to create initial space rooms", e);
|
||||||
setError(_t("Failed to create initial space rooms"));
|
setError(_t("Failed to create initial space rooms"));
|
||||||
|
@ -353,7 +482,10 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onClick = onFinished;
|
let onClick = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
onFinished(false);
|
||||||
|
};
|
||||||
let buttonLabel = _t("Skip for now");
|
let buttonLabel = _t("Skip for now");
|
||||||
if (roomNames.some(name => name.trim())) {
|
if (roomNames.some(name => name.trim())) {
|
||||||
onClick = onNextClick;
|
onClick = onNextClick;
|
||||||
|
@ -365,45 +497,26 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
<div className="mx_SpaceRoomView_description">{ description }</div>
|
<div className="mx_SpaceRoomView_description">{ description }</div>
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
|
<form onSubmit={onClick} id="mx_SpaceSetupFirstRooms">
|
||||||
{ fields }
|
{ fields }
|
||||||
|
</form>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary"
|
kind="primary"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
element="input"
|
||||||
{ buttonLabel }
|
type="submit"
|
||||||
</AccessibleButton>
|
form="mx_SpaceSetupFirstRooms"
|
||||||
|
value={buttonLabel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
||||||
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
|
||||||
|
|
||||||
const [busy, setBusy] = useState(false);
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
let onClick = 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"));
|
|
||||||
}
|
|
||||||
setBusy(false);
|
|
||||||
};
|
|
||||||
buttonLabel = busy ? _t("Adding...") : _t("Add");
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h1>{ _t("What do you want to organise?") }</h1>
|
<h1>{ _t("What do you want to organise?") }</h1>
|
||||||
<div className="mx_SpaceRoomView_description">
|
<div className="mx_SpaceRoomView_description">
|
||||||
|
@ -411,36 +524,28 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
||||||
"no one will be informed. You can add more later.") }
|
"no one will be informed. You can add more later.") }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
|
||||||
|
|
||||||
<AddExistingToSpace
|
<AddExistingToSpace
|
||||||
space={space}
|
space={space}
|
||||||
selected={selectedToAdd}
|
emptySelectionButton={
|
||||||
onChange={(checked, room) => {
|
<AccessibleButton kind="primary" onClick={onFinished}>
|
||||||
if (checked) {
|
{ _t("Skip for now") }
|
||||||
selectedToAdd.add(room);
|
</AccessibleButton>
|
||||||
} else {
|
|
||||||
selectedToAdd.delete(room);
|
|
||||||
}
|
}
|
||||||
setSelectedToAdd(new Set(selectedToAdd));
|
onFinished={onFinished}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton
|
|
||||||
kind="primary"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{ buttonLabel }
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceSetupPublicShare = ({ space, onFinished }) => {
|
const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRooms }) => {
|
||||||
return <div className="mx_SpaceRoomView_publicShare">
|
return <div className="mx_SpaceRoomView_publicShare">
|
||||||
<h1>{ _t("Share %(name)s", { name: space.name }) }</h1>
|
<h1>{ _t("Share %(name)s", {
|
||||||
|
name: justCreatedOpts?.createOpts?.name || space.name,
|
||||||
|
}) }</h1>
|
||||||
<div className="mx_SpaceRoomView_description">
|
<div className="mx_SpaceRoomView_description">
|
||||||
{ _t("It's just you at the moment, it will be even better with others.") }
|
{ _t("It's just you at the moment, it will be even better with others.") }
|
||||||
</div>
|
</div>
|
||||||
|
@ -449,17 +554,20 @@ const SpaceSetupPublicShare = ({ space, onFinished }) => {
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton kind="primary" onClick={onFinished}>
|
<AccessibleButton kind="primary" onClick={onFinished}>
|
||||||
{ _t("Go to my first room") }
|
{ createdRooms ? _t("Go to my first room") : _t("Go to my space") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceSetupPrivateScope = ({ space, onFinished }) => {
|
const SpaceSetupPrivateScope = ({ space, justCreatedOpts, onFinished }) => {
|
||||||
return <div className="mx_SpaceRoomView_privateScope">
|
return <div className="mx_SpaceRoomView_privateScope">
|
||||||
<h1>{ _t("Who are you working with?") }</h1>
|
<h1>{ _t("Who are you working with?") }</h1>
|
||||||
<div className="mx_SpaceRoomView_description">
|
<div className="mx_SpaceRoomView_description">
|
||||||
{ _t("Make sure the right people have access to %(name)s", { name: space.name }) }
|
{ _t("Make sure the right people have access to %(name)s", {
|
||||||
|
name: justCreatedOpts?.createOpts?.name || space.name,
|
||||||
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -476,6 +584,7 @@ const SpaceSetupPrivateScope = ({ space, onFinished }) => {
|
||||||
<h3>{ _t("Me and my teammates") }</h3>
|
<h3>{ _t("Me and my teammates") }</h3>
|
||||||
<div>{ _t("A private space for you and your teammates") }</div>
|
<div>{ _t("A private space for you and your teammates") }</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -506,10 +615,13 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
ref={fieldRefs[i]}
|
ref={fieldRefs[i]}
|
||||||
onValidate={validateEmailRules}
|
onValidate={validateEmailRules}
|
||||||
autoFocus={i === 0}
|
autoFocus={i === 0}
|
||||||
|
disabled={busy}
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNextClick = async () => {
|
const onNextClick = async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
setError("");
|
setError("");
|
||||||
for (let i = 0; i < fieldRefs.length; i++) {
|
for (let i = 0; i < fieldRefs.length; i++) {
|
||||||
const fieldRef = fieldRefs[i];
|
const fieldRef = fieldRefs[i];
|
||||||
|
@ -543,7 +655,10 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onClick = onFinished;
|
let onClick = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
let buttonLabel = _t("Skip for now");
|
let buttonLabel = _t("Skip for now");
|
||||||
if (emailAddresses.some(name => name.trim())) {
|
if (emailAddresses.some(name => name.trim())) {
|
||||||
onClick = onNextClick;
|
onClick = onNextClick;
|
||||||
|
@ -556,8 +671,21 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
{ _t("Make sure the right people have access. You can invite more later.") }
|
{ _t("Make sure the right people have access. You can invite more later.") }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomView_inviteTeammates_betaDisclaimer">
|
||||||
|
<BetaPill onClick={onBetaClick} />
|
||||||
|
{ _t("<b>This is an experimental feature.</b> For now, " +
|
||||||
|
"new users receiving an invite will have to open the invite on <link/> to actually join.", {}, {
|
||||||
|
b: sub => <b>{ sub }</b>,
|
||||||
|
link: () => <a href="https://app.element.io/" rel="noreferrer noopener" target="_blank">
|
||||||
|
app.element.io
|
||||||
|
</a>,
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
|
<form onSubmit={onClick} id="mx_SpaceSetupPrivateInvite">
|
||||||
{ fields }
|
{ fields }
|
||||||
|
</form>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_inviteTeammates_buttons">
|
<div className="mx_SpaceRoomView_inviteTeammates_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -569,10 +697,17 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton kind="primary" disabled={busy} onClick={onClick}>
|
<AccessibleButton
|
||||||
{ buttonLabel }
|
kind="primary"
|
||||||
</AccessibleButton>
|
disabled={busy}
|
||||||
|
onClick={onClick}
|
||||||
|
element="input"
|
||||||
|
type="submit"
|
||||||
|
form="mx_SpaceSetupPrivateInvite"
|
||||||
|
value={buttonLabel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -671,7 +806,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
let suggestedRooms = SpaceStore.instance.suggestedRooms;
|
let suggestedRooms = SpaceStore.instance.suggestedRooms;
|
||||||
if (SpaceStore.instance.activeSpace !== this.props.space) {
|
if (SpaceStore.instance.activeSpace !== this.props.space) {
|
||||||
// the space store has the suggested rooms loaded for a different space, fetch the right ones
|
// the space store has the suggested rooms loaded for a different space, fetch the right ones
|
||||||
suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1)).rooms;
|
suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suggestedRooms.length) {
|
if (suggestedRooms.length) {
|
||||||
|
@ -679,9 +814,11 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: "view_room",
|
action: "view_room",
|
||||||
room_id: room.room_id,
|
room_id: room.room_id,
|
||||||
|
room_alias: room.canonical_alias || room.aliases?.[0],
|
||||||
|
via_servers: room.viaServers,
|
||||||
oobData: {
|
oobData: {
|
||||||
avatarUrl: room.avatar_url,
|
avatarUrl: room.avatar_url,
|
||||||
name: room.name || room.canonical_alias || room.aliases.pop() || _t("Empty room"),
|
name: room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -693,7 +830,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
private renderBody() {
|
private renderBody() {
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case Phase.Landing:
|
case Phase.Landing:
|
||||||
if (this.state.myMembership === "join") {
|
if (this.state.myMembership === "join" && SettingsStore.getValue("feature_spaces")) {
|
||||||
return <SpaceLanding space={this.props.space} />;
|
return <SpaceLanding space={this.props.space} />;
|
||||||
} else {
|
} else {
|
||||||
return <SpacePreview
|
return <SpacePreview
|
||||||
|
@ -706,20 +843,26 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
return <SpaceSetupFirstRooms
|
return <SpaceSetupFirstRooms
|
||||||
space={this.props.space}
|
space={this.props.space}
|
||||||
title={_t("What are some things you want to discuss in %(spaceName)s?", {
|
title={_t("What are some things you want to discuss in %(spaceName)s?", {
|
||||||
spaceName: this.props.space.name,
|
spaceName: this.props.justCreatedOpts?.createOpts?.name || this.props.space.name,
|
||||||
})}
|
})}
|
||||||
description={
|
description={
|
||||||
_t("Let's create a room for each of them.") + "\n" +
|
_t("Let's create a room for each of them.") + "\n" +
|
||||||
_t("You can add more later too, including already existing ones.")
|
_t("You can add more later too, including already existing ones.")
|
||||||
}
|
}
|
||||||
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.PublicShare, createdRooms })}
|
||||||
/>;
|
/>;
|
||||||
case Phase.PublicShare:
|
case Phase.PublicShare:
|
||||||
return <SpaceSetupPublicShare space={this.props.space} onFinished={this.goToFirstRoom} />;
|
return <SpaceSetupPublicShare
|
||||||
|
justCreatedOpts={this.props.justCreatedOpts}
|
||||||
|
space={this.props.space}
|
||||||
|
onFinished={this.goToFirstRoom}
|
||||||
|
createdRooms={this.state.createdRooms}
|
||||||
|
/>;
|
||||||
|
|
||||||
case Phase.PrivateScope:
|
case Phase.PrivateScope:
|
||||||
return <SpaceSetupPrivateScope
|
return <SpaceSetupPrivateScope
|
||||||
space={this.props.space}
|
space={this.props.space}
|
||||||
|
justCreatedOpts={this.props.justCreatedOpts}
|
||||||
onFinished={(invite: boolean) => {
|
onFinished={(invite: boolean) => {
|
||||||
this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateExistingRooms });
|
this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateExistingRooms });
|
||||||
}}
|
}}
|
||||||
|
@ -735,7 +878,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
title={_t("What projects are you working on?")}
|
title={_t("What projects are you working on?")}
|
||||||
description={_t("We'll create rooms for each of them. " +
|
description={_t("We'll create rooms for each of them. " +
|
||||||
"You can add more later too, including already existing ones.")}
|
"You can add more later too, including already existing ones.")}
|
||||||
onFinished={() => this.setState({ phase: Phase.Landing })}
|
onFinished={(createdRooms: boolean) => this.setState({ phase: Phase.Landing, createdRooms })}
|
||||||
/>;
|
/>;
|
||||||
case Phase.PrivateExistingRooms:
|
case Phase.PrivateExistingRooms:
|
||||||
return <SpaceAddExistingRooms
|
return <SpaceAddExistingRooms
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||||
import {UIFeature} from "../../settings/UIFeature";
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
import {objectHasDiff} from "../../utils/objects";
|
import {objectHasDiff} from "../../utils/objects";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import { arrayFastClone } from "../../utils/arrays";
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -68,6 +69,7 @@ class TimelinePanel extends React.Component {
|
||||||
showReadReceipts: PropTypes.bool,
|
showReadReceipts: PropTypes.bool,
|
||||||
// Enable managing RRs and RMs. These require the timelineSet to have a room.
|
// Enable managing RRs and RMs. These require the timelineSet to have a room.
|
||||||
manageReadReceipts: PropTypes.bool,
|
manageReadReceipts: PropTypes.bool,
|
||||||
|
sendReadReceiptOnLoad: PropTypes.bool,
|
||||||
manageReadMarkers: PropTypes.bool,
|
manageReadMarkers: PropTypes.bool,
|
||||||
|
|
||||||
// true to give the component a 'display: none' style.
|
// true to give the component a 'display: none' style.
|
||||||
|
@ -126,6 +128,7 @@ class TimelinePanel extends React.Component {
|
||||||
// event tile heights. (See _unpaginateEvents)
|
// event tile heights. (See _unpaginateEvents)
|
||||||
timelineCap: Number.MAX_VALUE,
|
timelineCap: Number.MAX_VALUE,
|
||||||
className: 'mx_RoomView_messagePanel',
|
className: 'mx_RoomView_messagePanel',
|
||||||
|
sendReadReceiptOnLoad: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -785,8 +788,10 @@ class TimelinePanel extends React.Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
|
const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
|
||||||
this._setReadMarker(lastDisplayedEvent.getId(),
|
this._setReadMarker(
|
||||||
lastDisplayedEvent.getTs());
|
lastDisplayedEvent.getId(),
|
||||||
|
lastDisplayedEvent.getTs(),
|
||||||
|
);
|
||||||
|
|
||||||
// the read-marker should become invisible, so that if the user scrolls
|
// the read-marker should become invisible, so that if the user scrolls
|
||||||
// down, they don't see it.
|
// down, they don't see it.
|
||||||
|
@ -1049,7 +1054,9 @@ class TimelinePanel extends React.Component {
|
||||||
this._messagePanel.current.scrollToBottom();
|
this._messagePanel.current.scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.sendReadReceiptOnLoad) {
|
||||||
this.sendReadReceipt();
|
this.sendReadReceipt();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1135,6 +1142,17 @@ class TimelinePanel extends React.Component {
|
||||||
// get the list of events from the timeline window and the pending event list
|
// get the list of events from the timeline window and the pending event list
|
||||||
_getEvents() {
|
_getEvents() {
|
||||||
const events = this._timelineWindow.getEvents();
|
const events = this._timelineWindow.getEvents();
|
||||||
|
|
||||||
|
// `arrayFastClone` performs a shallow copy of the array
|
||||||
|
// we want the last event to be decrypted first but displayed last
|
||||||
|
// `reverse` is destructive and unfortunately mutates the "events" array
|
||||||
|
arrayFastClone(events)
|
||||||
|
.reverse()
|
||||||
|
.forEach(event => {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
client.decryptEventIfNeeded(event);
|
||||||
|
});
|
||||||
|
|
||||||
const firstVisibleEventIndex = this._checkForPreJoinUISI(events);
|
const firstVisibleEventIndex = this._checkForPreJoinUISI(events);
|
||||||
|
|
||||||
// Hold onto the live events separately. The read receipt and read marker
|
// Hold onto the live events separately. The read receipt and read marker
|
||||||
|
|
|
@ -179,7 +179,7 @@ const BaseAvatar = (props: IProps) => {
|
||||||
width: toPx(width),
|
width: toPx(width),
|
||||||
height: toPx(height),
|
height: toPx(height),
|
||||||
}}
|
}}
|
||||||
title={title} alt=""
|
title={title} alt={_t("Avatar")}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
{...otherProps} />
|
{...otherProps} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { User } from "matrix-js-sdk/src/models/user";
|
import { User } from "matrix-js-sdk/src/models/user";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
import { TagID } from '../../../stores/room-list/models';
|
|
||||||
import RoomAvatar from "./RoomAvatar";
|
import RoomAvatar from "./RoomAvatar";
|
||||||
import NotificationBadge from '../rooms/NotificationBadge';
|
import NotificationBadge from '../rooms/NotificationBadge';
|
||||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
|
@ -35,7 +34,6 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
avatarSize: number;
|
avatarSize: number;
|
||||||
tag: TagID;
|
|
||||||
displayBadge?: boolean;
|
displayBadge?: boolean;
|
||||||
forceCount?: boolean;
|
forceCount?: boolean;
|
||||||
oobData?: object;
|
oobData?: object;
|
||||||
|
|
108
src/components/views/beta/BetaCard.tsx
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 classNames from "classnames";
|
||||||
|
|
||||||
|
import {_t} from "../../../languageHandler";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||||
|
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
title?: string;
|
||||||
|
featureId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BetaPill = ({ onClick }: { onClick?: () => void }) => {
|
||||||
|
if (onClick) {
|
||||||
|
return <TextWithTooltip
|
||||||
|
class={classNames("mx_BetaCard_betaPill", {
|
||||||
|
mx_BetaCard_betaPill_clickable: !!onClick,
|
||||||
|
})}
|
||||||
|
tooltip={<div>
|
||||||
|
<div className="mx_Tooltip_title">
|
||||||
|
{ _t("Spaces is a beta feature") }
|
||||||
|
</div>
|
||||||
|
<div className="mx_Tooltip_sub">
|
||||||
|
{ _t("Tap for more info") }
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
onClick={onClick}
|
||||||
|
tooltipProps={{ yOffset: -10 }}
|
||||||
|
>
|
||||||
|
{ _t("Beta") }
|
||||||
|
</TextWithTooltip>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span
|
||||||
|
className={classNames("mx_BetaCard_betaPill", {
|
||||||
|
mx_BetaCard_betaPill_clickable: !!onClick,
|
||||||
|
})}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{ _t("Beta") }
|
||||||
|
</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
|
||||||
|
const info = SettingsStore.getBetaInfo(featureId);
|
||||||
|
if (!info) return null; // Beta is invalid/disabled
|
||||||
|
|
||||||
|
const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading } = info;
|
||||||
|
const value = SettingsStore.getValue(featureId);
|
||||||
|
|
||||||
|
let feedbackButton;
|
||||||
|
if (value && feedbackLabel && feedbackSubheading && SdkConfig.get().bug_report_endpoint_url) {
|
||||||
|
feedbackButton = <AccessibleButton
|
||||||
|
onClick={() => {
|
||||||
|
Modal.createTrackedDialog("Beta Feedback", featureId, BetaFeedbackDialog, { featureId });
|
||||||
|
}}
|
||||||
|
kind="primary"
|
||||||
|
>
|
||||||
|
{ _t("Feedback") }
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_BetaCard">
|
||||||
|
<div>
|
||||||
|
<h3 className="mx_BetaCard_title">
|
||||||
|
{ titleOverride || _t(title) }
|
||||||
|
<BetaPill />
|
||||||
|
</h3>
|
||||||
|
<span className="mx_BetaCard_caption">{ _t(caption) }</span>
|
||||||
|
<div>
|
||||||
|
{ feedbackButton }
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={() => SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
|
||||||
|
kind={feedbackButton ? "primary_outline" : "primary"}
|
||||||
|
>
|
||||||
|
{ value ? _t("Leave the beta") : _t("Join the beta") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
{ disclaimer && <div className="mx_BetaCard_disclaimer">
|
||||||
|
{ disclaimer(value) }
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
<img src={image} alt="" />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BetaCard;
|
|
@ -78,8 +78,10 @@ export default class MessageContextMenu extends React.Component {
|
||||||
|
|
||||||
// We explicitly decline to show the redact option on ACL events as it has a potential
|
// We explicitly decline to show the redact option on ACL events as it has a potential
|
||||||
// to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
|
// to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
|
||||||
|
// Similarly for encryption events, since redacting them "breaks everything"
|
||||||
const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
|
const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
|
||||||
&& this.props.mxEvent.getType() !== EventType.RoomServerAcl;
|
&& this.props.mxEvent.getType() !== EventType.RoomServerAcl
|
||||||
|
&& this.props.mxEvent.getType() !== EventType.RoomEncryption;
|
||||||
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
|
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
|
||||||
|
|
||||||
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
|
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useContext, useState} from "react";
|
import React, {ReactNode, useContext, useMemo, useState} from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
|
@ -29,11 +29,16 @@ import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
import {getDisplayAliasForRoom} from "../../../Rooms";
|
import {getDisplayAliasForRoom} from "../../../Rooms";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
import {allSettled} from "../../../utils/promise";
|
import {sleep} from "../../../utils/promise";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import {calculateRoomVia} from "../../../utils/permalinks/Permalinks";
|
import {calculateRoomVia} from "../../../utils/permalinks/Permalinks";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||||
|
import ProgressBar from "../elements/ProgressBar";
|
||||||
|
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
|
||||||
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
@ -43,32 +48,59 @@ interface IProps extends IDialogProps {
|
||||||
|
|
||||||
const Entry = ({ room, checked, onChange }) => {
|
const Entry = ({ room, checked, onChange }) => {
|
||||||
return <label className="mx_AddExistingToSpace_entry">
|
return <label className="mx_AddExistingToSpace_entry">
|
||||||
<RoomAvatar room={room} height={32} width={32} />
|
{ room?.isSpaceRoom()
|
||||||
|
? <RoomAvatar room={room} height={32} width={32} />
|
||||||
|
: <DecoratedRoomAvatar room={room} avatarSize={32} />
|
||||||
|
}
|
||||||
<span className="mx_AddExistingToSpace_entry_name">{ room.name }</span>
|
<span className="mx_AddExistingToSpace_entry_name">{ room.name }</span>
|
||||||
<StyledCheckbox onChange={(e) => onChange(e.target.checked)} checked={checked} />
|
<StyledCheckbox
|
||||||
|
onChange={onChange ? (e) => onChange(e.target.checked) : null}
|
||||||
|
checked={checked}
|
||||||
|
disabled={!onChange}
|
||||||
|
/>
|
||||||
</label>;
|
</label>;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IAddExistingToSpaceProps {
|
interface IAddExistingToSpaceProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
selected: Set<Room>;
|
footerPrompt?: ReactNode;
|
||||||
onChange(checked: boolean, room: Room): void;
|
emptySelectionButton?: ReactNode;
|
||||||
|
onFinished(added: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space, selected, onChange }) => {
|
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
|
space,
|
||||||
|
footerPrompt,
|
||||||
|
emptySelectionButton,
|
||||||
|
onFinished,
|
||||||
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const [query, setQuery] = useState("");
|
const visibleRooms = useMemo(() => cli.getVisibleRooms().filter(r => r.getMyMembership() === "join"), [cli]);
|
||||||
const lcQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
||||||
const existingSubspacesSet = new Set(existingSubspaces);
|
const [progress, setProgress] = useState<number>(null);
|
||||||
const existingRoomsSet = new Set(SpaceStore.instance.getChildRooms(space.roomId));
|
const [error, setError] = useState<Error>(null);
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
|
||||||
|
const existingSubspacesSet = useMemo(() => new Set(SpaceStore.instance.getChildSpaces(space.roomId)), [space]);
|
||||||
|
const existingRoomsSet = useMemo(() => new Set(SpaceStore.instance.getChildRooms(space.roomId)), [space]);
|
||||||
|
|
||||||
|
const [spaces, rooms, dms] = useMemo(() => {
|
||||||
|
let rooms = visibleRooms;
|
||||||
|
|
||||||
|
if (lcQuery) {
|
||||||
|
const matcher = new QueryMatcher<Room>(visibleRooms, {
|
||||||
|
keys: ["name"],
|
||||||
|
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
||||||
|
shouldMatchWordsOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
rooms = matcher.match(lcQuery);
|
||||||
|
}
|
||||||
|
|
||||||
const joinRule = space.getJoinRule();
|
const joinRule = space.getJoinRule();
|
||||||
const [spaces, rooms, dms] = cli.getVisibleRooms().reduce((arr, room) => {
|
return sortRooms(rooms).reduce((arr, room) => {
|
||||||
if (room.getMyMembership() !== "join") return arr;
|
|
||||||
if (!room.name.toLowerCase().includes(lcQuery)) return arr;
|
|
||||||
|
|
||||||
if (room.isSpaceRoom()) {
|
if (room.isSpaceRoom()) {
|
||||||
if (room !== space && !existingSubspacesSet.has(room)) {
|
if (room !== space && !existingSubspacesSet.has(room)) {
|
||||||
arr[0].push(room);
|
arr[0].push(room);
|
||||||
|
@ -83,6 +115,94 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}, [[], [], []]);
|
}, [[], [], []]);
|
||||||
|
}, [visibleRooms, space, lcQuery, existingRoomsSet, existingSubspacesSet]);
|
||||||
|
|
||||||
|
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 = <>
|
||||||
|
<img
|
||||||
|
src={require("../../../../res/img/element-icons/warning-badge.svg")}
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className="mx_AddExistingToSpaceDialog_error">
|
||||||
|
<div className="mx_AddExistingToSpaceDialog_errorHeading">{ _t("Not all selected were added") }</div>
|
||||||
|
<div className="mx_AddExistingToSpaceDialog_errorCaption">{ _t("Try again") }</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<AccessibleButton className="mx_AddExistingToSpaceDialog_retryButton" onClick={addRooms}>
|
||||||
|
{ _t("Retry") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</>;
|
||||||
|
} else if (busy) {
|
||||||
|
footer = <span>
|
||||||
|
<ProgressBar value={progress} max={selectedToAdd.size} />
|
||||||
|
<div className="mx_AddExistingToSpaceDialog_progressText">
|
||||||
|
{ _t("Adding rooms... (%(progress)s out of %(count)s)", {
|
||||||
|
count: selectedToAdd.size,
|
||||||
|
progress,
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
</span>;
|
||||||
|
} else {
|
||||||
|
let button = emptySelectionButton;
|
||||||
|
if (!button || selectedToAdd.size > 0) {
|
||||||
|
button = <AccessibleButton kind="primary" disabled={selectedToAdd.size < 1} onClick={addRooms}>
|
||||||
|
{ _t("Add") }
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer = <>
|
||||||
|
<span>
|
||||||
|
{ footerPrompt }
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{ button }
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = !busy && !error ? (checked, room) => {
|
||||||
|
if (checked) {
|
||||||
|
selectedToAdd.add(room);
|
||||||
|
} else {
|
||||||
|
selectedToAdd.delete(room);
|
||||||
|
}
|
||||||
|
setSelectedToAdd(new Set(selectedToAdd));
|
||||||
|
} : null;
|
||||||
|
|
||||||
return <div className="mx_AddExistingToSpace">
|
return <div className="mx_AddExistingToSpace">
|
||||||
<SearchBox
|
<SearchBox
|
||||||
|
@ -100,10 +220,10 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
|
||||||
return <Entry
|
return <Entry
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
room={room}
|
room={room}
|
||||||
checked={selected.has(room)}
|
checked={selectedToAdd.has(room)}
|
||||||
onChange={(checked) => {
|
onChange={onChange ? (checked) => {
|
||||||
onChange(checked, room);
|
onChange(checked, room);
|
||||||
}}
|
} : null}
|
||||||
/>;
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,14 +232,18 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
|
||||||
{ spaces.length > 0 ? (
|
{ spaces.length > 0 ? (
|
||||||
<div className="mx_AddExistingToSpace_section mx_AddExistingToSpace_section_spaces">
|
<div className="mx_AddExistingToSpace_section mx_AddExistingToSpace_section_spaces">
|
||||||
<h3>{ _t("Spaces") }</h3>
|
<h3>{ _t("Spaces") }</h3>
|
||||||
|
<div className="mx_AddExistingToSpace_section_experimental">
|
||||||
|
<div>{ _t("Feeling experimental?") }</div>
|
||||||
|
<div>{ _t("You can add existing spaces to a space.") }</div>
|
||||||
|
</div>
|
||||||
{ spaces.map(space => {
|
{ spaces.map(space => {
|
||||||
return <Entry
|
return <Entry
|
||||||
key={space.roomId}
|
key={space.roomId}
|
||||||
room={space}
|
room={space}
|
||||||
checked={selected.has(space)}
|
checked={selectedToAdd.has(space)}
|
||||||
onChange={(checked) => {
|
onChange={onChange ? (checked) => {
|
||||||
onChange(checked, space);
|
onChange(checked, space);
|
||||||
}}
|
} : null}
|
||||||
/>;
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,10 +256,10 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
|
||||||
return <Entry
|
return <Entry
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
room={room}
|
room={room}
|
||||||
checked={selected.has(room)}
|
checked={selectedToAdd.has(room)}
|
||||||
onChange={(checked) => {
|
onChange={onChange ? (checked) => {
|
||||||
onChange(checked, room);
|
onChange(checked, room);
|
||||||
}}
|
} : null}
|
||||||
/>;
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
@ -145,16 +269,16 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({ space,
|
||||||
{ _t("No results") }
|
{ _t("No results") }
|
||||||
</span> : undefined }
|
</span> : undefined }
|
||||||
</AutoHideScrollbar>
|
</AutoHideScrollbar>
|
||||||
|
|
||||||
|
<div className="mx_AddExistingToSpace_footer">
|
||||||
|
{ footer }
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
|
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
|
||||||
const [selectedSpace, setSelectedSpace] = useState(space);
|
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
||||||
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
|
||||||
|
|
||||||
const [busy, setBusy] = useState(false);
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
let spaceOptionSection;
|
let spaceOptionSection;
|
||||||
if (existingSubspaces.length > 0) {
|
if (existingSubspaces.length > 0) {
|
||||||
|
@ -201,51 +325,20 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
>
|
>
|
||||||
{ error && <div className="mx_AddExistingToSpaceDialog_errorText">{ error }</div> }
|
|
||||||
|
|
||||||
<MatrixClientContext.Provider value={cli}>
|
<MatrixClientContext.Provider value={cli}>
|
||||||
<AddExistingToSpace
|
<AddExistingToSpace
|
||||||
space={space}
|
space={space}
|
||||||
selected={selectedToAdd}
|
onFinished={onFinished}
|
||||||
onChange={(checked, room) => {
|
footerPrompt={<>
|
||||||
if (checked) {
|
<div>{ _t("Want to add a new room instead?") }</div>
|
||||||
selectedToAdd.add(room);
|
|
||||||
} else {
|
|
||||||
selectedToAdd.delete(room);
|
|
||||||
}
|
|
||||||
setSelectedToAdd(new Set(selectedToAdd));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</MatrixClientContext.Provider>
|
|
||||||
|
|
||||||
<div className="mx_AddExistingToSpaceDialog_footer">
|
|
||||||
<span>
|
|
||||||
<div>{ _t("Don't want to add an existing room?") }</div>
|
|
||||||
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
|
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
|
||||||
{ _t("Create a new room") }
|
{ _t("Create a new room") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</span>
|
</>}
|
||||||
|
/>
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
|
|
||||||
<AccessibleButton
|
<SpaceFeedbackPrompt onClick={() => onFinished(false)} />
|
||||||
kind="primary"
|
|
||||||
disabled={busy || selectedToAdd.size < 1}
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
setBusy(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ busy ? _t("Adding...") : _t("Add") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
106
src/components/views/dialogs/BetaFeedbackDialog.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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, {useState} from "react";
|
||||||
|
|
||||||
|
import QuestionDialog from './QuestionDialog';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
import {IDialogProps} from "./IDialogProps";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {submitFeedback} from "../../../rageshake/submit-rageshake";
|
||||||
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import InfoDialog from "./InfoDialog";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
import {USER_LABS_TAB} from "./UserSettingsDialog";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
featureId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BetaFeedbackDialog: React.FC<IProps> = ({featureId, onFinished}) => {
|
||||||
|
const info = SettingsStore.getBetaInfo(featureId);
|
||||||
|
|
||||||
|
const [comment, setComment] = useState("");
|
||||||
|
const [canContact, setCanContact] = useState(false);
|
||||||
|
|
||||||
|
const sendFeedback = async (ok: boolean) => {
|
||||||
|
if (!ok) return onFinished(false);
|
||||||
|
|
||||||
|
submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact);
|
||||||
|
onFinished(true);
|
||||||
|
|
||||||
|
Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, {
|
||||||
|
title: _t("Beta feedback"),
|
||||||
|
description: _t("Thank you for your feedback, we really appreciate it."),
|
||||||
|
button: _t("Done"),
|
||||||
|
hasCloseButton: false,
|
||||||
|
fixedWidth: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (<QuestionDialog
|
||||||
|
className="mx_BetaFeedbackDialog"
|
||||||
|
hasCancelButton={true}
|
||||||
|
title={_t("%(featureName)s beta feedback", { featureName: info.title })}
|
||||||
|
description={<React.Fragment>
|
||||||
|
<div className="mx_BetaFeedbackDialog_subheading">
|
||||||
|
{ _t(info.feedbackSubheading) }
|
||||||
|
|
||||||
|
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.")}
|
||||||
|
|
||||||
|
<AccessibleButton kind="link" onClick={() => {
|
||||||
|
onFinished(false);
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: Action.ViewUserSettings,
|
||||||
|
initialTabId: USER_LABS_TAB,
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
{ _t("To leave the beta, visit your settings.") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id="feedbackComment"
|
||||||
|
label={_t("Feedback")}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={comment}
|
||||||
|
element="textarea"
|
||||||
|
onChange={(ev) => {
|
||||||
|
setComment(ev.target.value);
|
||||||
|
}}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StyledCheckbox
|
||||||
|
checked={canContact}
|
||||||
|
onClick={e => setCanContact((e.target as HTMLInputElement).checked)}
|
||||||
|
>
|
||||||
|
{ _t("You may contact me if you have any follow up questions") }
|
||||||
|
</StyledCheckbox>
|
||||||
|
</React.Fragment>}
|
||||||
|
button={_t("Send feedback")}
|
||||||
|
buttonDisabled={!comment}
|
||||||
|
onFinished={sendFeedback}
|
||||||
|
/>);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BetaFeedbackDialog;
|
|
@ -39,9 +39,12 @@ export default class ConfirmWipeDeviceDialog extends React.Component {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_ConfirmWipeDeviceDialog' hasCancel={true}
|
<BaseDialog
|
||||||
|
className='mx_ConfirmWipeDeviceDialog'
|
||||||
|
hasCancel={true}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Clear all data in this session?")}>
|
title={_t("Clear all data in this session?")}
|
||||||
|
>
|
||||||
<div className='mx_ConfirmWipeDeviceDialog_content'>
|
<div className='mx_ConfirmWipeDeviceDialog_content'>
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_t(
|
||||||
|
|
|
@ -70,8 +70,16 @@ class GenericEditor extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
textInput(id, label) {
|
textInput(id, label) {
|
||||||
return <Field id={id} label={label} size="42" autoFocus={true} type="text" autoComplete="on"
|
return <Field
|
||||||
value={this.state[id]} onChange={this._onChange} />;
|
id={id}
|
||||||
|
label={label}
|
||||||
|
size="42"
|
||||||
|
autoFocus={true}
|
||||||
|
type="text"
|
||||||
|
autoComplete="on"
|
||||||
|
value={this.state[id]}
|
||||||
|
onChange={this._onChange}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,12 @@ export default class IntegrationsDisabledDialog extends React.Component {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_IntegrationsDisabledDialog' hasCancel={true}
|
<BaseDialog
|
||||||
|
className='mx_IntegrationsDisabledDialog'
|
||||||
|
hasCancel={true}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Integrations are disabled")}>
|
title={_t("Integrations are disabled")}
|
||||||
|
>
|
||||||
<div className='mx_IntegrationsDisabledDialog_content'>
|
<div className='mx_IntegrationsDisabledDialog_content'>
|
||||||
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
|
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,9 +37,12 @@ export default class IntegrationsImpossibleDialog extends React.Component {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_IntegrationsImpossibleDialog' hasCancel={false}
|
<BaseDialog
|
||||||
|
className='mx_IntegrationsImpossibleDialog'
|
||||||
|
hasCancel={false}
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Integrations not allowed")}>
|
title={_t("Integrations not allowed")}
|
||||||
|
>
|
||||||
<div className='mx_IntegrationsImpossibleDialog_content'>
|
<div className='mx_IntegrationsImpossibleDialog_content'>
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_t(
|
||||||
|
|
|
@ -1312,7 +1312,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
goButtonFn = this._startDm;
|
goButtonFn = this._startDm;
|
||||||
} else if (this.props.kind === KIND_INVITE) {
|
} else if (this.props.kind === KIND_INVITE) {
|
||||||
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
||||||
const isSpace = room?.isSpaceRoom();
|
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
|
||||||
title = isSpace
|
title = isSpace
|
||||||
? _t("Invite to %(spaceName)s", {
|
? _t("Invite to %(spaceName)s", {
|
||||||
spaceName: room.name || _t("Unnamed Space"),
|
spaceName: room.name || _t("Unnamed Space"),
|
||||||
|
|
|
@ -164,8 +164,12 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_MessageEditHistoryDialog' hasCancel={true}
|
<BaseDialog
|
||||||
onFinished={this.props.onFinished} title={_t("Message edits")}>
|
className='mx_MessageEditHistoryDialog'
|
||||||
|
hasCancel={true}
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={_t("Message edits")}
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|