Merge branch 'feed' into feed-audio
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ package-lock.json
|
||||||
/src/component-index.js
|
/src/component-index.js
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.tmp
|
||||||
|
|
123
CHANGELOG.md
|
@ -1,3 +1,126 @@
|
||||||
|
Changes in [3.16.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.16.0) (2021-03-15)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.16.0-rc.2...v3.16.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 9.9.0
|
||||||
|
* [Release] Change read receipt drift to be non-fractional
|
||||||
|
[\#5746](https://github.com/matrix-org/matrix-react-sdk/pull/5746)
|
||||||
|
* [Release] Properly gate SpaceRoomView behind labs
|
||||||
|
[\#5750](https://github.com/matrix-org/matrix-react-sdk/pull/5750)
|
||||||
|
|
||||||
|
Changes in [3.16.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.16.0-rc.2) (2021-03-10)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.16.0-rc.1...v3.16.0-rc.2)
|
||||||
|
|
||||||
|
* Fixed incorrect build output in rc.1
|
||||||
|
|
||||||
|
Changes in [3.16.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.16.0-rc.1) (2021-03-10)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0...v3.16.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 9.9.0-rc.1
|
||||||
|
* Translations update from Weblate
|
||||||
|
[\#5743](https://github.com/matrix-org/matrix-react-sdk/pull/5743)
|
||||||
|
* Document behaviour of showReadReceipts=false for sent receipts
|
||||||
|
[\#5739](https://github.com/matrix-org/matrix-react-sdk/pull/5739)
|
||||||
|
* Tweak sent marker code style
|
||||||
|
[\#5741](https://github.com/matrix-org/matrix-react-sdk/pull/5741)
|
||||||
|
* Fix sent markers disappearing for edits/reactions
|
||||||
|
[\#5737](https://github.com/matrix-org/matrix-react-sdk/pull/5737)
|
||||||
|
* Ignore to-device decryption in the room list store
|
||||||
|
[\#5740](https://github.com/matrix-org/matrix-react-sdk/pull/5740)
|
||||||
|
* Spaces suggested rooms support
|
||||||
|
[\#5736](https://github.com/matrix-org/matrix-react-sdk/pull/5736)
|
||||||
|
* Add tooltips to sent/sending receipts
|
||||||
|
[\#5738](https://github.com/matrix-org/matrix-react-sdk/pull/5738)
|
||||||
|
* Remove a bunch of useless 'use strict' definitions
|
||||||
|
[\#5735](https://github.com/matrix-org/matrix-react-sdk/pull/5735)
|
||||||
|
* [SK-1] Fix types for replaceableComponent
|
||||||
|
[\#5732](https://github.com/matrix-org/matrix-react-sdk/pull/5732)
|
||||||
|
* [SK-2] Make debugging skinning problems easier
|
||||||
|
[\#5733](https://github.com/matrix-org/matrix-react-sdk/pull/5733)
|
||||||
|
* Support sending invite reasons with /invite command
|
||||||
|
[\#5695](https://github.com/matrix-org/matrix-react-sdk/pull/5695)
|
||||||
|
* Fix clicking on the avatar for opening member info requires pixel-perfect
|
||||||
|
accuracy
|
||||||
|
[\#5717](https://github.com/matrix-org/matrix-react-sdk/pull/5717)
|
||||||
|
* Display decrypted and encrypted event source on the same dialog
|
||||||
|
[\#5713](https://github.com/matrix-org/matrix-react-sdk/pull/5713)
|
||||||
|
* Fix units of TURN server expiry time
|
||||||
|
[\#5730](https://github.com/matrix-org/matrix-react-sdk/pull/5730)
|
||||||
|
* Display room name in pills instead of address
|
||||||
|
[\#5624](https://github.com/matrix-org/matrix-react-sdk/pull/5624)
|
||||||
|
* Refresh UI for file uploads
|
||||||
|
[\#5723](https://github.com/matrix-org/matrix-react-sdk/pull/5723)
|
||||||
|
* UI refresh for uploaded files
|
||||||
|
[\#5719](https://github.com/matrix-org/matrix-react-sdk/pull/5719)
|
||||||
|
* Improve message sending states to match new designs
|
||||||
|
[\#5699](https://github.com/matrix-org/matrix-react-sdk/pull/5699)
|
||||||
|
* Add clipboard write permission for widgets
|
||||||
|
[\#5725](https://github.com/matrix-org/matrix-react-sdk/pull/5725)
|
||||||
|
* Fix widget resizing
|
||||||
|
[\#5722](https://github.com/matrix-org/matrix-react-sdk/pull/5722)
|
||||||
|
* Option for audio streaming
|
||||||
|
[\#5707](https://github.com/matrix-org/matrix-react-sdk/pull/5707)
|
||||||
|
* Show a specific error for hs_disabled
|
||||||
|
[\#5576](https://github.com/matrix-org/matrix-react-sdk/pull/5576)
|
||||||
|
* Add Edge to the targets list
|
||||||
|
[\#5721](https://github.com/matrix-org/matrix-react-sdk/pull/5721)
|
||||||
|
* File drop UI fixes and improvements
|
||||||
|
[\#5505](https://github.com/matrix-org/matrix-react-sdk/pull/5505)
|
||||||
|
* Fix Bottom border of state counters is white on the dark theme
|
||||||
|
[\#5715](https://github.com/matrix-org/matrix-react-sdk/pull/5715)
|
||||||
|
* Trim spurious whitespace of nicknames
|
||||||
|
[\#5332](https://github.com/matrix-org/matrix-react-sdk/pull/5332)
|
||||||
|
* Ensure HostSignupDialog border colour matches light theme
|
||||||
|
[\#5716](https://github.com/matrix-org/matrix-react-sdk/pull/5716)
|
||||||
|
* Don't place another call if there's already one ongoing
|
||||||
|
[\#5712](https://github.com/matrix-org/matrix-react-sdk/pull/5712)
|
||||||
|
* Space room hierarchies
|
||||||
|
[\#5706](https://github.com/matrix-org/matrix-react-sdk/pull/5706)
|
||||||
|
* Iterate Space view and right panel
|
||||||
|
[\#5705](https://github.com/matrix-org/matrix-react-sdk/pull/5705)
|
||||||
|
* Add a scroll to bottom on message sent setting
|
||||||
|
[\#5692](https://github.com/matrix-org/matrix-react-sdk/pull/5692)
|
||||||
|
* Add .tmp files to gitignore
|
||||||
|
[\#5708](https://github.com/matrix-org/matrix-react-sdk/pull/5708)
|
||||||
|
* Initial Space Room View and Creation UX
|
||||||
|
[\#5704](https://github.com/matrix-org/matrix-react-sdk/pull/5704)
|
||||||
|
* Add multi language spell check
|
||||||
|
[\#5452](https://github.com/matrix-org/matrix-react-sdk/pull/5452)
|
||||||
|
* Fix tetris effect (holes) in read receipts
|
||||||
|
[\#5697](https://github.com/matrix-org/matrix-react-sdk/pull/5697)
|
||||||
|
* Fixed edit for markdown images
|
||||||
|
[\#5703](https://github.com/matrix-org/matrix-react-sdk/pull/5703)
|
||||||
|
* Iterate Space Panel
|
||||||
|
[\#5702](https://github.com/matrix-org/matrix-react-sdk/pull/5702)
|
||||||
|
* Fix read receipts for compact layout
|
||||||
|
[\#5700](https://github.com/matrix-org/matrix-react-sdk/pull/5700)
|
||||||
|
* Space Store and Space Panel for Room List filtering
|
||||||
|
[\#5689](https://github.com/matrix-org/matrix-react-sdk/pull/5689)
|
||||||
|
* Log when turn creds expire
|
||||||
|
[\#5691](https://github.com/matrix-org/matrix-react-sdk/pull/5691)
|
||||||
|
* Null check for maxHeight in call view
|
||||||
|
[\#5690](https://github.com/matrix-org/matrix-react-sdk/pull/5690)
|
||||||
|
* Autocomplete invited users
|
||||||
|
[\#5687](https://github.com/matrix-org/matrix-react-sdk/pull/5687)
|
||||||
|
* Add send message button
|
||||||
|
[\#5535](https://github.com/matrix-org/matrix-react-sdk/pull/5535)
|
||||||
|
* Move call buttons to the room header
|
||||||
|
[\#5693](https://github.com/matrix-org/matrix-react-sdk/pull/5693)
|
||||||
|
* Use the default SSSS key if the default is set
|
||||||
|
[\#5638](https://github.com/matrix-org/matrix-react-sdk/pull/5638)
|
||||||
|
* Initial Spaces feature flag
|
||||||
|
[\#5668](https://github.com/matrix-org/matrix-react-sdk/pull/5668)
|
||||||
|
* Clean up code edge cases and add helpers
|
||||||
|
[\#5667](https://github.com/matrix-org/matrix-react-sdk/pull/5667)
|
||||||
|
* Clean up widgets when leaving the room
|
||||||
|
[\#5684](https://github.com/matrix-org/matrix-react-sdk/pull/5684)
|
||||||
|
* Fix read receipts?
|
||||||
|
[\#5567](https://github.com/matrix-org/matrix-react-sdk/pull/5567)
|
||||||
|
* Fix MAU usage alerts
|
||||||
|
[\#5678](https://github.com/matrix-org/matrix-react-sdk/pull/5678)
|
||||||
|
|
||||||
Changes in [3.15.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.15.0) (2021-03-01)
|
Changes in [3.15.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.15.0) (2021-03-01)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0-rc.1...v3.15.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0-rc.1...v3.15.0)
|
||||||
|
|
|
@ -3,12 +3,15 @@ module.exports = {
|
||||||
"presets": [
|
"presets": [
|
||||||
["@babel/preset-env", {
|
["@babel/preset-env", {
|
||||||
"targets": [
|
"targets": [
|
||||||
"last 2 Chrome versions", "last 2 Firefox versions", "last 2 Safari versions"
|
"last 2 Chrome versions",
|
||||||
|
"last 2 Firefox versions",
|
||||||
|
"last 2 Safari versions",
|
||||||
|
"last 2 Edge versions",
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
"@babel/preset-typescript",
|
"@babel/preset-typescript",
|
||||||
"@babel/preset-flow",
|
"@babel/preset-flow",
|
||||||
"@babel/preset-react"
|
"@babel/preset-react",
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
["@babel/plugin-proposal-decorators", {legacy: true}],
|
["@babel/plugin-proposal-decorators", {legacy: true}],
|
||||||
|
@ -18,6 +21,6 @@ module.exports = {
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
"@babel/plugin-transform-flow-comments",
|
"@babel/plugin-transform-flow-comments",
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"@babel/plugin-transform-runtime"
|
"@babel/plugin-transform-runtime",
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
19
docs/media-handling.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Media handling
|
||||||
|
|
||||||
|
Surely media should be as easy as just putting a URL into an `img` and calling it good, right?
|
||||||
|
Not quite. Matrix uses something called a Matrix Content URI (better known as MXC URI) to identify
|
||||||
|
content, which is then converted to a regular HTTPS URL on the homeserver. However, sometimes that
|
||||||
|
URL can change depending on deployment considerations.
|
||||||
|
|
||||||
|
The react-sdk features a [customisation endpoint](https://github.com/vector-im/element-web/blob/develop/docs/customisations.md)
|
||||||
|
for media handling where all conversions from MXC URI to HTTPS URL happen. This is to ensure that
|
||||||
|
those obscure deployments can route all their media to the right place.
|
||||||
|
|
||||||
|
For development, there are currently two functions available: `mediaFromMxc` and `mediaFromContent`.
|
||||||
|
The `mediaFromMxc` function should be self-explanatory. `mediaFromContent` takes an event content as
|
||||||
|
a parameter and will automatically parse out the source media and thumbnail. Both functions return
|
||||||
|
a `Media` object with a number of options on it, such as getting various common HTTPS URLs for the
|
||||||
|
media.
|
||||||
|
|
||||||
|
**It is extremely important that all media calls are put through this customisation endpoint.** So
|
||||||
|
much so it's a lint rule to avoid accidental use of the wrong functions.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.15.0",
|
"version": "3.16.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
"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",
|
||||||
"velocity-animate": "^1.5.2",
|
"velocity-animate": "^2.0.6",
|
||||||
"what-input": "^5.2.10",
|
"what-input": "^5.2.10",
|
||||||
"zxcvbn": "^4.4.2"
|
"zxcvbn": "^4.4.2"
|
||||||
},
|
},
|
||||||
|
@ -157,6 +157,7 @@
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"jest-canvas-mock": "^2.3.0",
|
"jest-canvas-mock": "^2.3.0",
|
||||||
"jest-environment-jsdom-sixteen": "^1.0.3",
|
"jest-environment-jsdom-sixteen": "^1.0.3",
|
||||||
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"matrix-mock-request": "^1.2.3",
|
"matrix-mock-request": "^1.2.3",
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||||
|
|
|
@ -489,54 +489,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
margin-top: 69px;
|
margin-top: 69px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Beta {
|
|
||||||
color: red;
|
|
||||||
margin-right: 10px;
|
|
||||||
position: relative;
|
|
||||||
top: -3px;
|
|
||||||
background-color: white;
|
|
||||||
padding: 0 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid darkred;
|
|
||||||
cursor: help;
|
|
||||||
transition-duration: 200ms;
|
|
||||||
font-size: smaller;
|
|
||||||
filter: opacity(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_Beta:hover {
|
|
||||||
color: white;
|
|
||||||
border: 1px solid gray;
|
|
||||||
background-color: darkred;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TintableSvgButton {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TintableSvgButton object {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TintableSvgButton span {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// username colors
|
// username colors
|
||||||
// used by SenderProfile & RoomPreviewBar
|
// used by SenderProfile & RoomPreviewBar
|
||||||
.mx_Username_color1 {
|
.mx_Username_color1 {
|
||||||
|
@ -606,6 +558,13 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@define-mixin ProgressBarBgColour $colour {
|
||||||
|
background-color: $colour;
|
||||||
|
&::-webkit-progress-bar {
|
||||||
|
background-color: $colour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@define-mixin ProgressBarBorderRadius $radius {
|
@define-mixin ProgressBarBorderRadius $radius {
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
&::-moz-progress-bar {
|
&::-moz-progress-bar {
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
@import "./structures/_ScrollPanel.scss";
|
@import "./structures/_ScrollPanel.scss";
|
||||||
@import "./structures/_SearchBox.scss";
|
@import "./structures/_SearchBox.scss";
|
||||||
@import "./structures/_SpacePanel.scss";
|
@import "./structures/_SpacePanel.scss";
|
||||||
|
@import "./structures/_SpaceRoomDirectory.scss";
|
||||||
|
@import "./structures/_SpaceRoomView.scss";
|
||||||
@import "./structures/_TabbedView.scss";
|
@import "./structures/_TabbedView.scss";
|
||||||
@import "./structures/_ToastContainer.scss";
|
@import "./structures/_ToastContainer.scss";
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
|
@ -57,6 +59,7 @@
|
||||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
@import "./views/context_menus/_TagTileContextMenu.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/_BugReportDialog.scss";
|
@import "./views/dialogs/_BugReportDialog.scss";
|
||||||
|
@ -90,6 +93,7 @@
|
||||||
@import "./views/dialogs/_SettingsDialog.scss";
|
@import "./views/dialogs/_SettingsDialog.scss";
|
||||||
@import "./views/dialogs/_ShareDialog.scss";
|
@import "./views/dialogs/_ShareDialog.scss";
|
||||||
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
|
@import "./views/dialogs/_SlashCommandHelpDialog.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/_UploadConfirmDialog.scss";
|
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
||||||
|
@ -213,6 +217,7 @@
|
||||||
@import "./views/settings/_DevicesPanel.scss";
|
@import "./views/settings/_DevicesPanel.scss";
|
||||||
@import "./views/settings/_E2eAdvancedPanel.scss";
|
@import "./views/settings/_E2eAdvancedPanel.scss";
|
||||||
@import "./views/settings/_EmailAddresses.scss";
|
@import "./views/settings/_EmailAddresses.scss";
|
||||||
|
@import "./views/settings/_SpellCheckLanguages.scss";
|
||||||
@import "./views/settings/_IntegrationManager.scss";
|
@import "./views/settings/_IntegrationManager.scss";
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
@import "./views/settings/_PhoneNumbers.scss";
|
@import "./views/settings/_PhoneNumbers.scss";
|
||||||
|
@ -233,6 +238,9 @@
|
||||||
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
|
||||||
|
@import "./views/spaces/_SpaceBasicSettings.scss";
|
||||||
|
@import "./views/spaces/_SpaceCreateMenu.scss";
|
||||||
|
@import "./views/spaces/_SpacePublicShare.scss";
|
||||||
@import "./views/terms/_InlineTermsAgreement.scss";
|
@import "./views/terms/_InlineTermsAgreement.scss";
|
||||||
@import "./views/toasts/_AnalyticsToast.scss";
|
@import "./views/toasts/_AnalyticsToast.scss";
|
||||||
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
|
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,3 +160,20 @@ limitations under the License.
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RightPanel_scopeHeader {
|
||||||
|
margin: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,35 +20,54 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes mx_RoomView_fileDropTarget_animation {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomView_fileDropTarget {
|
.mx_RoomView_fileDropTarget {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
font-size: $font-18px;
|
font-size: $font-18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
padding-left: 12px;
|
background-color: $primary-bg-color;
|
||||||
padding-right: 12px;
|
opacity: 0.95;
|
||||||
margin-left: -12px;
|
|
||||||
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
|
|
||||||
background-color: $droptarget-bg-color;
|
|
||||||
border: 2px #e1dddd solid;
|
|
||||||
border-bottom: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 52px;
|
|
||||||
bottom: 0px;
|
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
animation: mx_RoomView_fileDropTarget_animation;
|
||||||
|
animation-duration: 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_fileDropTargetLabel {
|
@keyframes mx_RoomView_fileDropTarget_image_animation {
|
||||||
top: 50%;
|
from {
|
||||||
width: 100%;
|
width: 0px;
|
||||||
margin-top: -50px;
|
}
|
||||||
position: absolute;
|
to {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomView_fileDropTarget_image {
|
||||||
|
animation: mx_RoomView_fileDropTarget_image_animation;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_auxPanel {
|
.mx_RoomView_auxPanel {
|
||||||
|
@ -117,7 +136,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_body {
|
.mx_RoomView_body {
|
||||||
position: relative; //for .mx_RoomView_auxPanel_fullHeight
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
|
@ -16,9 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
$topLevelHeight: 32px;
|
$topLevelHeight: 32px;
|
||||||
$nestedHeight: 24px;
|
$nestedHeight: 24px;
|
||||||
$gutterSize: 17px;
|
$gutterSize: 16px;
|
||||||
$activeStripeSize: 4px;
|
$activeBorderTransparentGap: 1px;
|
||||||
$activeBorderTransparentGap: 2px;
|
|
||||||
|
|
||||||
$activeBackgroundColor: $roomtile-selected-bg-color;
|
$activeBackgroundColor: $roomtile-selected-bg-color;
|
||||||
$activeBorderColor: $secondary-fg-color;
|
$activeBorderColor: $secondary-fg-color;
|
||||||
|
@ -36,6 +35,7 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_SpacePanel_spaceTreeWrapper {
|
.mx_SpacePanel_spaceTreeWrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_toggleCollapse {
|
.mx_SpacePanel_toggleCollapse {
|
||||||
|
@ -63,21 +63,26 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AutoHideScrollbar {
|
.mx_AutoHideScrollbar {
|
||||||
padding: 16px 12px 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_toggleCollapse {
|
.mx_SpaceButton_toggleCollapse {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceItem.collapsed {
|
.mx_SpaceTreeLevel {
|
||||||
.mx_SpaceButton {
|
display: flex;
|
||||||
.mx_NotificationBadge {
|
flex-direction: column;
|
||||||
right: -4px;
|
max-width: 250px;
|
||||||
top: -4px;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
.mx_SpaceItem {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceItem.collapsed {
|
||||||
& > .mx_SpaceButton > .mx_SpaceButton_toggleCollapse {
|
& > .mx_SpaceButton > .mx_SpaceButton_toggleCollapse {
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
@ -89,34 +94,43 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_SpaceItem:not(.hasSubSpaces) > .mx_SpaceButton {
|
.mx_SpaceItem:not(.hasSubSpaces) > .mx_SpaceButton {
|
||||||
margin-left: $gutterSize;
|
margin-left: $gutterSize;
|
||||||
|
min-width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton {
|
.mx_SpaceButton {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
position: relative;
|
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px;
|
padding: 4px 4px 4px 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&.mx_SpaceButton_active {
|
&.mx_SpaceButton_active {
|
||||||
&:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper {
|
&:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper {
|
||||||
background-color: $activeBackgroundColor;
|
background-color: $activeBackgroundColor;
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_SpaceButton_narrow {
|
&.mx_SpaceButton_narrow .mx_SpaceButton_selectionWrapper {
|
||||||
.mx_BaseAvatar, .mx_SpaceButton_avatarPlaceholder {
|
padding: $activeBorderTransparentGap;
|
||||||
border: 2px $activeBorderColor solid;
|
border: 3px $activeBorderColor solid;
|
||||||
border-radius: 11px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_selectionWrapper {
|
.mx_SpaceButton_selectionWrapper {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.mx_SpaceButton_narrow) {
|
||||||
|
.mx_SpaceButton_selectionWrapper {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_name {
|
.mx_SpaceButton_name {
|
||||||
|
@ -124,7 +138,6 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 150px;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
@ -133,8 +146,10 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_toggleCollapse {
|
.mx_SpaceButton_toggleCollapse {
|
||||||
width: calc($gutterSize - $activeStripeSize);
|
width: $gutterSize;
|
||||||
margin-left: 1px;
|
// negative margin to place it correctly even with the complex
|
||||||
|
// 4px selection border each space button has when active
|
||||||
|
margin-right: -4px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 20px;
|
mask-size: 20px;
|
||||||
|
@ -172,33 +187,58 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_avatarPlaceholder {
|
&.mx_SpaceButton_new .mx_SpaceButton_icon {
|
||||||
border: $activeBorderTransparentGap transparent solid;
|
background-color: $accent-color;
|
||||||
padding: $activeBorderTransparentGap;
|
transition: all .1s ease-in-out; // TODO transition
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: #ffffff;
|
||||||
|
mask-image: url('$(res)/img/element-icons/plus.svg');
|
||||||
|
transition: all .2s ease-in-out; // TODO transition
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
&.mx_SpaceButton_newCancel .mx_SpaceButton_icon {
|
||||||
/* moving the border-radius to this element from _image
|
background-color: $icon-button-color;
|
||||||
element so we can add a border to it without the initials being displaced */
|
|
||||||
overflow: hidden;
|
|
||||||
border: 2px transparent solid;
|
|
||||||
padding: $activeBorderTransparentGap;
|
|
||||||
|
|
||||||
.mx_BaseAvatar_initial {
|
&::before {
|
||||||
top: $activeBorderTransparentGap;
|
transform: rotate(45deg);
|
||||||
left: $activeBorderTransparentGap;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
.mx_BaseAvatar_image {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceButton_menuButton {
|
||||||
|
width: 20px;
|
||||||
|
min-width: 20px; // yay flex
|
||||||
|
height: 20px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
content: '';
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
||||||
|
background: $primary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
|
position: absolute;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
// don't set width so that it takes no space when there is no badge to show
|
|
||||||
margin: auto 0; // vertically align
|
|
||||||
|
|
||||||
// Create a flexbox to make aligning dot badges easier
|
// Create a flexbox to make aligning dot badges easier
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -218,14 +258,25 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
.mx_SpaceButton {
|
.mx_SpaceButton {
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
position: absolute;
|
right: -3px;
|
||||||
right: 0px;
|
top: -3px;
|
||||||
top: 2px;
|
}
|
||||||
|
|
||||||
|
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
|
||||||
|
// 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
|
||||||
|
right: -6px;
|
||||||
|
top: -6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.collapsed) {
|
&:not(.collapsed) {
|
||||||
|
.mx_SpacePanel_badgeContainer {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceButton:hover,
|
.mx_SpaceButton:hover,
|
||||||
.mx_SpaceButton:focus-within,
|
.mx_SpaceButton:focus-within,
|
||||||
.mx_SpaceButton_hasMenuOpen {
|
.mx_SpaceButton_hasMenuOpen {
|
||||||
|
@ -235,6 +286,10 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
height: 0;
|
height: 0;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceButton_menuButton {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,3 +308,50 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_contextMenu {
|
||||||
|
.mx_SpacePanel_contextMenu_header {
|
||||||
|
margin: 12px 16px 12px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IconizedContextMenu_optionList .mx_AccessibleButton.mx_SpacePanel_contextMenu_inviteButton {
|
||||||
|
color: $accent-color;
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconInvite::before {
|
||||||
|
background-color: $accent-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconSettings::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconLeave::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconHome::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/home.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconMembers::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconPlus::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/plus.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconExplore::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mx_SpacePanel_sharePublicSpace {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
229
res/css/structures/_SpaceRoomDirectory.scss
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
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_SpaceRoomDirectory_dialogWrapper > .mx_Dialog {
|
||||||
|
max-width: 960px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory {
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
word-break: break-word;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.mx_Dialog_title {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> h1 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dialog_content {
|
||||||
|
// TODO fix scrollbar
|
||||||
|
//display: flex;
|
||||||
|
//flex-direction: column;
|
||||||
|
//height: calc(100% - 80px);
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SearchBox {
|
||||||
|
margin: 24px 0 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_listHeader {
|
||||||
|
display: flex;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin: auto 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_list {
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomCount {
|
||||||
|
> h3 {
|
||||||
|
display: inline;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_subspace {
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_subspace_info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_actions {
|
||||||
|
text-align: right;
|
||||||
|
height: min-content;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_subspace_children {
|
||||||
|
margin-left: 12px;
|
||||||
|
border-left: 2px solid $space-button-outline-color;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid $space-button-outline-color;
|
||||||
|
margin: 8px 0 16px;
|
||||||
|
display: flex;
|
||||||
|
min-height: 76px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.mx_AccessibleButton:hover {
|
||||||
|
background-color: rgba(141, 151, 165, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 16px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile_info {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $font-15px;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: min-content;
|
||||||
|
margin: auto 0;
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile_name {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
line-height: $font-18px;
|
||||||
|
}
|
||||||
|
.mx_SpaceRoomDirectory_roomTile_topic {
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile_memberCount {
|
||||||
|
position: relative;
|
||||||
|
margin: auto 0 auto 24px;
|
||||||
|
padding: 0 0 0 28px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_actions {
|
||||||
|
width: 180px;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
& + .mx_AccessibleButton {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_actions {
|
||||||
|
.mx_SpaceRoomDirectory_actionsText {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
437
res/css/structures/_SpaceRoomView.scss
Normal file
|
@ -0,0 +1,437 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$SpaceRoomViewInnerWidth: 428px;
|
||||||
|
|
||||||
|
.mx_SpaceRoomView {
|
||||||
|
.mx_MainSplit > div:first-child {
|
||||||
|
padding: 80px 60px;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: $font-24px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_description {
|
||||||
|
font-size: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_buttons {
|
||||||
|
display: block;
|
||||||
|
margin-top: 44px;
|
||||||
|
width: $SpaceRoomViewInnerWidth;
|
||||||
|
text-align: right; // button alignment right
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
padding: 8px 22px;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field {
|
||||||
|
max-width: $SpaceRoomViewInnerWidth;
|
||||||
|
|
||||||
|
& + .mx_Field {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_errorText {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview {
|
||||||
|
padding: 32px 24px !important; // override default padding from above
|
||||||
|
margin: auto;
|
||||||
|
max-width: 480px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 2px 15px 30px $dialog-shadow-color;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_inviter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_inviter_name {
|
||||||
|
line-height: $font-18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_inviter_mxid {
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_BaseAvatar_image,
|
||||||
|
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.mx_SpaceRoomView_preview_name {
|
||||||
|
margin: 20px 0 !important; // override default margin from above
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_info {
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_info_public,
|
||||||
|
.mx_SpaceRoomView_preview_info_private {
|
||||||
|
padding-left: 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: 0;
|
||||||
|
left: -2px;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_info_public::before {
|
||||||
|
mask-size: 12px;
|
||||||
|
mask-image: url("$(res)/img/globe.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_info_private::before {
|
||||||
|
mask-size: 14px;
|
||||||
|
mask-image: url("$(res)/img/element-icons/lock.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
color: inherit;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "·"; // visual separator
|
||||||
|
position: absolute;
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_topic {
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
margin: 20px 0;
|
||||||
|
max-height: 160px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_preview_joinButtons {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
width: 200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 14px 0;
|
||||||
|
|
||||||
|
& + .mx_AccessibleButton {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing {
|
||||||
|
> .mx_BaseAvatar_image,
|
||||||
|
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_name {
|
||||||
|
margin: 24px 0 16px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_nameRow {
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
> h1 {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_inviter {
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_memberCount {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 24px;
|
||||||
|
padding: 0 0 0 28px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
background-color: $accent-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_topic {
|
||||||
|
font-size: $font-15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_adminButtons {
|
||||||
|
margin-top: 32px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
position: relative;
|
||||||
|
width: 160px;
|
||||||
|
height: 124px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 72px 16px 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid $space-button-outline-color;
|
||||||
|
margin-right: 28px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
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_inviteButton {
|
||||||
|
&::before {
|
||||||
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.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_SpaceRoomDirectory_list {
|
||||||
|
max-width: 600px;
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile_actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_privateScope {
|
||||||
|
.mx_RadioButton {
|
||||||
|
width: $SpaceRoomViewInnerWidth;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid $space-button-outline-color;
|
||||||
|
padding: 16px 16px 16px 72px;
|
||||||
|
margin-top: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> div:first-of-type {
|
||||||
|
// hide radio dot
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RadioButton_content {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
line-height: $font-18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
top: 24px;
|
||||||
|
left: 20px;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RadioButton_checked {
|
||||||
|
border-color: $accent-color;
|
||||||
|
|
||||||
|
.mx_RadioButton_content {
|
||||||
|
> div {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_privateScope_justMeButton::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_privateScope_meAndMyTeammatesButton::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_inviteTeammates {
|
||||||
|
.mx_SpaceRoomView_inviteTeammates_buttons {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
margin-top: 28px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 32px;
|
||||||
|
line-height: 24px; // to center icons
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .mx_AccessibleButton {
|
||||||
|
margin-left: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_inviteTeammates_inviteDialogButton::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016, 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.
|
||||||
|
@ -15,47 +15,45 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_UploadBar {
|
.mx_UploadBar {
|
||||||
|
padding-left: 65px; // line up with the shield area in the composer
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.mx_ProgressBar {
|
||||||
|
width: calc(100% - 40px); // cheating at a right margin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UploadBar_uploadProgressOuter {
|
.mx_UploadBar_filename {
|
||||||
height: 5px;
|
|
||||||
margin-left: 63px;
|
|
||||||
margin-top: -1px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UploadBar_uploadProgressInner {
|
|
||||||
background-color: $accent-color;
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UploadBar_uploadFilename {
|
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-left: 65px;
|
color: $muted-fg-color;
|
||||||
opacity: 0.5;
|
|
||||||
color: $primary-fg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UploadBar_uploadIcon {
|
|
||||||
float: left;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-left: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UploadBar_uploadCancel {
|
|
||||||
float: right;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-right: 10px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
opacity: 0.6;
|
padding-left: 22px; // 18px for icon, 4px for padding
|
||||||
cursor: pointer;
|
font-size: $font-15px;
|
||||||
z-index: 1;
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $muted-fg-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/upload.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UploadBar_uploadBytes {
|
.mx_UploadBar_cancel {
|
||||||
float: right;
|
position: absolute;
|
||||||
margin-top: 5px;
|
top: 0;
|
||||||
margin-right: 30px;
|
right: 0;
|
||||||
color: $accent-color;
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-right: 16px; // align over rightmost button in composer
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $muted-fg-color;
|
||||||
|
mask-image: url('$(res)/img/icons-close.svg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,18 @@ limitations under the License.
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ViewSource_label_bottom {
|
.mx_ViewSource_separator {
|
||||||
clear: both;
|
clear: both;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
padding-top: 0.7em;
|
||||||
|
padding-bottom: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ViewSource_heading {
|
||||||
|
font-size: $font-17px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
margin-top: 0.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ViewSource pre {
|
.mx_ViewSource pre {
|
||||||
|
@ -34,3 +43,7 @@ limitations under the License.
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ViewSource_details {
|
||||||
|
margin-top: 0.8em;
|
||||||
|
}
|
||||||
|
|
|
@ -26,50 +26,6 @@ limitations under the License.
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients {
|
|
||||||
width: max-content;
|
|
||||||
margin: 36px auto 0;
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_desktop, .mx_CompleteSecurity_clients_mobile {
|
|
||||||
position: relative;
|
|
||||||
width: 160px;
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 64px;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
height: 48px;
|
|
||||||
width: 48px;
|
|
||||||
left: 56px;
|
|
||||||
top: 0;
|
|
||||||
background-color: $muted-fg-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_desktop {
|
|
||||||
margin-right: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_desktop::before {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/monitor.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_mobile::before {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/smartphone.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-size: $font-12px;
|
|
||||||
color: $muted-fg-color;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_heroIcon {
|
.mx_CompleteSecurity_heroIcon {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
|
|
|
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// XXX: We shouldn't be using TemporaryTile anywhere - delete it.
|
.mx_DecoratedRoomAvatar, .mx_ExtraTile {
|
||||||
.mx_DecoratedRoomAvatar, .mx_TemporaryTile {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
|
&.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
|
||||||
|
|
185
res/css/views/dialogs/_AddExistingToSpaceDialog.scss
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
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_AddExistingToSpaceDialog_wrapper {
|
||||||
|
.mx_Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog {
|
||||||
|
width: 480px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.mx_Dialog_title {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 5px 16px 5px 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> h1 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_onlySpace {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dropdown_input {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
> .mx_Dropdown_option {
|
||||||
|
padding-left: 0;
|
||||||
|
flex: unset;
|
||||||
|
height: unset;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dropdown_menu {
|
||||||
|
.mx_AddExistingToSpaceDialog_dropdownOptionActive {
|
||||||
|
color: $accent-color;
|
||||||
|
padding-right: 32px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: 8px;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
background-color: $accent-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SearchBox {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_errorText {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_content {
|
||||||
|
.mx_AddExistingToSpaceDialog_noResults {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_section {
|
||||||
|
margin-top: 24px;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
line-height: $font-15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_entry {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_entry_name {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: 30px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
min-width: 92px;
|
||||||
|
font-weight: normal;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_section_spaces {
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpaceDialog_footer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 32px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
padding: 8px 22px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,11 @@ limitations under the License.
|
||||||
max-width: 580px;
|
max-width: 580px;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
|
// Ensure dialog borders are always white as the HostSignupDialog
|
||||||
|
// does not yet support dark mode or theming in general.
|
||||||
|
// In the future we might want to pass the theme to the called
|
||||||
|
// iframe, should some hosting provider have that need.
|
||||||
|
background-color: #ffffff;
|
||||||
|
|
||||||
.mx_HostSignupDialog_info {
|
.mx_HostSignupDialog_info {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
55
res/css/views/dialogs/_SpaceSettingsDialog.scss
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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_SpaceSettingsDialog {
|
||||||
|
width: 480px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
|
||||||
|
.mx_SpaceSettings_errorText {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ToggleSwitch {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceSettingsDialog_buttons {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 64px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
padding: 8px 22px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,9 @@ limitations under the License.
|
||||||
padding: 7px 18px;
|
padding: 7px 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,4 +33,10 @@ limitations under the License.
|
||||||
color: $notice-primary-color;
|
color: $notice-primary-color;
|
||||||
background-color: $notice-primary-bg-color;
|
background-color: $notice-primary-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_AccessibleButton_kind_secondary {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
border: 1px solid $secondary-fg-color;
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 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.
|
||||||
|
@ -15,15 +15,15 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
progress.mx_ProgressBar {
|
progress.mx_ProgressBar {
|
||||||
height: 4px;
|
height: 6px;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border: 0;
|
border: none;
|
||||||
|
|
||||||
@mixin ProgressBarBorderRadius "10px";
|
@mixin ProgressBarBorderRadius "6px";
|
||||||
@mixin ProgressBarColour $accent-color;
|
@mixin ProgressBarColour $progressbar-fg-color;
|
||||||
|
@mixin ProgressBarBgColour $progressbar-bg-color;
|
||||||
::-webkit-progress-value {
|
::-webkit-progress-value {
|
||||||
transition: width 1s;
|
transition: width 1s;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016, 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.
|
||||||
|
@ -16,6 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MFileBody_download {
|
.mx_MFileBody_download {
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
|
|
||||||
|
.mx_MFileBody_download_icon {
|
||||||
|
// 12px instead of 14px to better match surrounding font size
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
mask-size: 12px;
|
||||||
|
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url("$(res)/img/download.svg");
|
||||||
|
background-color: $accent-color;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MFileBody_download a {
|
.mx_MFileBody_download a {
|
||||||
|
@ -45,3 +58,46 @@ limitations under the License.
|
||||||
* big the content of the iframe is. */
|
* big the content of the iframe is. */
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MFileBody_info {
|
||||||
|
background-color: $message-body-panel-bg-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 270px;
|
||||||
|
padding: 8px;
|
||||||
|
color: $message-body-panel-fg-color;
|
||||||
|
|
||||||
|
.mx_MFileBody_info_icon {
|
||||||
|
background-color: $message-body-panel-icon-bg-color;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 12px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: cover;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
||||||
|
background-color: $message-body-panel-fg-color;
|
||||||
|
width: 13px;
|
||||||
|
height: 15px;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MFileBody_info_filename {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
width: calc(100% - 32px - 12px); // 32px icon, 12px margin on the icon
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,13 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_UserInfo {
|
.mx_EncryptionInfo_spinner {
|
||||||
.mx_EncryptionInfo_spinner {
|
.mx_Spinner {
|
||||||
.mx_Spinner {
|
margin-top: 25px;
|
||||||
margin-top: 25px;
|
margin-bottom: 15px;
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,11 +370,6 @@ $MinWidth: 240px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avoid apptile iframes capturing mouse event focus when resizing */
|
|
||||||
.mx_AppsDrawer_resizing iframe {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper {
|
.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
.m_RoomView_auxPanel_stateViews {
|
.m_RoomView_auxPanel_stateViews {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
padding-left: 19px;
|
padding-left: 19px;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid $primary-hairline-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m_RoomView_auxPanel_stateViews_span a {
|
.m_RoomView_auxPanel_stateViews_span a {
|
||||||
|
|
|
@ -213,23 +213,36 @@ $left-gutter: 64px;
|
||||||
color: $accent-fg-color;
|
color: $accent-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_encrypting {
|
|
||||||
color: $event-encrypting-color !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_sending {
|
|
||||||
color: $event-sending-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_sending .mx_UserPill,
|
|
||||||
.mx_EventTile_sending .mx_RoomPill {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_notSent {
|
.mx_EventTile_notSent {
|
||||||
color: $event-notsent-color;
|
color: $event-notsent-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_receiptSent,
|
||||||
|
.mx_EventTile_receiptSending {
|
||||||
|
// We don't use `position: relative` on the element because then it won't line
|
||||||
|
// up with the other read receipts
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 14px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mx_EventTile_receiptSent::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/circle-sent.svg');
|
||||||
|
}
|
||||||
|
.mx_EventTile_receiptSending::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_contextual {
|
.mx_EventTile_contextual {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ $left-gutter: 64px;
|
||||||
.mx_EventTile {
|
.mx_EventTile {
|
||||||
> .mx_SenderProfile {
|
> .mx_SenderProfile {
|
||||||
line-height: $font-20px;
|
line-height: $font-20px;
|
||||||
padding-left: $left-gutter;
|
margin-left: $left-gutter;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .mx_EventTile_line {
|
> .mx_EventTile_line {
|
||||||
|
|
|
@ -181,8 +181,7 @@ $irc-line-height: $font-18px;
|
||||||
> span {
|
> span {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .mx_SenderProfile_name,
|
> .mx_SenderProfile_name {
|
||||||
> .mx_SenderProfile_aux {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
min-width: var(--name-width);
|
min-width: var(--name-width);
|
||||||
|
@ -212,8 +211,7 @@ $irc-line-height: $font-18px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
> .mx_SenderProfile_name,
|
> .mx_SenderProfile_name {
|
||||||
> .mx_SenderProfile_aux {
|
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberInfo_name {
|
.mx_MemberInfo_name {
|
||||||
|
|
|
@ -44,6 +44,12 @@ limitations under the License.
|
||||||
.mx_AutoHideScrollbar {
|
.mx_AutoHideScrollbar {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RightPanel_scopeHeader {
|
||||||
|
// vertically align with position on other right panel cards
|
||||||
|
// to prevent it bouncing as user navigates right panel
|
||||||
|
margin-top: -8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupMemberList_query,
|
.mx_GroupMemberList_query,
|
||||||
|
|
|
@ -19,7 +19,10 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomList_iconPlus::before {
|
.mx_RoomList_iconPlus::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/plus.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/plus-circle.svg');
|
||||||
|
}
|
||||||
|
.mx_RoomList_iconHash::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg');
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconExplore::before {
|
.mx_RoomList_iconExplore::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||||
|
|
35
res/css/views/settings/_SpellCheckLanguages.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
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_ExistingSpellCheckLanguage {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ExistingSpellCheckLanguage_language {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_GeneralUserSettingsTab_spellCheckLanguageInput {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpellCheckLanguages {
|
||||||
|
@mixin mx_Settings_fullWidthField;
|
||||||
|
}
|
86
res/css/views/spaces/_SpaceBasicSettings.scss
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
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_SpaceBasicSettings {
|
||||||
|
.mx_Field {
|
||||||
|
margin: 32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceBasicSettings_avatarContainer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 24px;
|
||||||
|
|
||||||
|
.mx_SpaceBasicSettings_avatar {
|
||||||
|
position: relative;
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.mx_SpaceBasicSettings_avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only show it when the button is a div and not an img (has avatar)
|
||||||
|
div.mx_SpaceBasicSettings_avatar {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #ffffff; // white icon fill
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 20px;
|
||||||
|
mask-image: url('$(res)/img/element-icons/camera.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_AccessibleButton_kind_link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0;
|
||||||
|
margin: auto 16px;
|
||||||
|
color: #368bd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_SpaceBasicSettings_avatar_remove {
|
||||||
|
color: $notice-primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
padding: 8px 22px;
|
||||||
|
margin-left: auto;
|
||||||
|
display: block;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
138
res/css/views/spaces/_SpaceCreateMenu.scss
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: the space panel currently does not have a fixed width,
|
||||||
|
// just the headers at each level have a max-width of 150px
|
||||||
|
// so this will look slightly off for now. We should probably use css grid for the whole main layout...
|
||||||
|
$spacePanelWidth: 200px;
|
||||||
|
|
||||||
|
.mx_SpaceCreateMenu_wrapper {
|
||||||
|
// background blur everything except SpacePanel
|
||||||
|
.mx_ContextualMenu_background {
|
||||||
|
background-color: $dialog-backdrop-color;
|
||||||
|
opacity: 0.6;
|
||||||
|
left: $spacePanelWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ContextualMenu {
|
||||||
|
padding: 24px;
|
||||||
|
width: 480px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> h2 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> p {
|
||||||
|
font-size: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceCreateMenuType {
|
||||||
|
position: relative;
|
||||||
|
padding: 16px 32px 16px 72px;
|
||||||
|
width: 432px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid $input-darker-bg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
top: 24px;
|
||||||
|
left: 20px;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 32px;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $accent-color;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceCreateMenuType_public::before {
|
||||||
|
mask-image: url('$(res)/img/globe.svg');
|
||||||
|
mask-size: 26px;
|
||||||
|
}
|
||||||
|
.mx_SpaceCreateMenuType_private::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/lock.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceCreateMenu_back {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
position: relative;
|
||||||
|
background-color: $theme-button-bg-color;
|
||||||
|
border-radius: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: $muted-fg-color;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: 2px 3px;
|
||||||
|
mask-size: 24px;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
padding: 8px 22px;
|
||||||
|
margin-left: auto;
|
||||||
|
display: block;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
res/css/views/spaces/_SpacePublicShare.scss
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
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_SpacePublicShare {
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
border: 1px solid $space-button-outline-color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 24px 12px 52px;
|
||||||
|
margin-top: 16px;
|
||||||
|
width: $SpaceRoomViewInnerWidth;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: #368bd6;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(141, 151, 165, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
background: $muted-fg-color;
|
||||||
|
left: 12px;
|
||||||
|
top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_SpacePublicShare_shareButton::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/link.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_SpacePublicShare_inviteButton::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
res/img/element-icons/circle-sending.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="8" cy="8" r="7.5" stroke="#8D99A5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 152 B |
4
res/img/element-icons/circle-sent.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z" fill="#8D99A5"/>
|
||||||
|
<path d="M11.8697 4.95309C11.6784 4.7576 11.3597 4.74731 11.1578 4.93251L6.62066 9.04804L4.95244 7.91627C4.7293 7.77223 4.42116 7.77223 4.20865 7.95742C3.95363 8.1632 3.93238 8.5336 4.14489 8.78053L6.06813 10.9206C6.1 10.9515 6.13188 10.9926 6.17438 11.0132C6.53565 11.3013 7.07756 11.2498 7.37508 10.9L7.40695 10.8589L11.891 5.60128C12.0397 5.41608 12.0397 5.13828 11.8697 4.95309Z" fill="#8D99A5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 784 B |
3
res/img/element-icons/link.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.5285 6.54089L13.0273 6.04207C14.4052 4.66426 16.6259 4.65104 17.9874 6.01253C19.349 7.37402 19.3357 9.59466 17.9579 10.9725L15.5878 13.3425C14.21 14.7203 11.9893 14.7335 10.6277 13.372M11.4717 17.4589L10.9727 17.9579C9.59481 19.3357 7.37409 19.349 6.01256 17.9875C4.65102 16.626 4.66426 14.4053 6.04211 13.0275L8.41203 10.6577C9.78988 9.27988 12.0106 9.26665 13.3721 10.6281" stroke="black" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 549 B |
3
res/img/element-icons/lock.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.1113 2.6665C11.1839 2.6665 8.00016 5.85026 8.00016 9.77762V13.3332L7.3335 13.3332C6.22893 13.3332 5.3335 14.2286 5.3335 15.3332V27.3332C5.3335 28.4377 6.22893 29.3332 7.3335 29.3332H24.6668C25.7714 29.3332 26.6668 28.4377 26.6668 27.3332V15.3332C26.6668 14.2286 25.7714 13.3332 24.6668 13.3332L24.0002 13.3332V9.77762C24.0002 5.85026 20.8164 2.6665 16.8891 2.6665H15.1113ZM20.4446 13.3332V9.77762C20.4446 7.81394 18.8527 6.22206 16.8891 6.22206H15.1113C13.1476 6.22206 11.5557 7.81394 11.5557 9.77762V13.3332H20.4446Z" fill="#8E99A4"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 692 B |
3
res/img/element-icons/plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.74986 3.55554C8.74986 3.14133 8.41408 2.80554 7.99986 2.80554C7.58565 2.80554 7.24986 3.14133 7.24986 3.55554V7.24999L3.55542 7.24999C3.14121 7.24999 2.80542 7.58577 2.80542 7.99999C2.80542 8.4142 3.14121 8.74999 3.55542 8.74999L7.24987 8.74999V12.4444C7.24987 12.8586 7.58565 13.1944 7.99987 13.1944C8.41408 13.1944 8.74987 12.8586 8.74987 12.4444V8.74999L12.4443 8.74999C12.8585 8.74999 13.1943 8.4142 13.1943 7.99999C13.1943 7.58577 12.8585 7.24999 12.4443 7.24999L8.74986 7.24999V3.55554Z" fill="#8E99A4"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 670 B |
7
res/img/element-icons/roomlist/hash-circle.svg
Normal file
After Width: | Height: | Size: 8.4 KiB |
3
res/img/element-icons/roomlist/plus-circle.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9C17 13.4183 13.4183 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9ZM5.25 9C5.25 8.58579 5.58579 8.25 6 8.25H8.25V6C8.25 5.58579 8.58579 5.25 9 5.25C9.41421 5.25 9.75 5.58579 9.75 6V8.25H12C12.4142 8.25 12.75 8.58579 12.75 9C12.75 9.41421 12.4142 9.75 12 9.75H9.75V12C9.75 12.4142 9.41421 12.75 9 12.75C8.58579 12.75 8.25 12.4142 8.25 12V9.75H6C5.58579 9.75 5.25 9.41421 5.25 9Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 587 B |
4
res/img/element-icons/upload.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.99902 14L8.99902 4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path d="M12.5352 7.52441L8.99944 4.00012L5.46373 7.52441" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 336 B |
|
@ -1,5 +0,0 @@
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 5C2 3.89543 2.89543 3 4 3H20C21.1046 3 22 3.89543 22 5V15C22 16.1046 21.1046 17 20 17H4C2.89543 17 2 16.1046 2 15V5Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M8 21H16" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M12 17V21" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 510 B |
|
@ -1,4 +0,0 @@
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4C5 2.89543 5.89543 2 7 2H17C18.1046 2 19 2.89543 19 4V20C19 21.1046 18.1046 22 17 22H7C5.89543 22 5 21.1046 5 20V4Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<circle cx="12" cy="18" r="1" fill="#2E2F32"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 386 B |
|
@ -1,19 +1,3 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="45px" height="59px" viewBox="-1 -1 45 59" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0ZM17.2511 6.97409C16.9775 6.68236 16.5885 6.50012 16.157 6.50012C15.793 6.50012 15.4593 6.62973 15.1996 6.84532C15.1545 6.88267 15.1115 6.92281 15.0707 6.96564L8.79618 13.5539C8.22485 14.1538 8.24801 15.1032 8.8479 15.6746C9.4478 16.2459 10.3973 16.2227 10.9686 15.6228L14.657 11.7501V23.0589C14.657 23.8874 15.3285 24.5589 16.157 24.5589C16.9854 24.5589 17.657 23.8874 17.657 23.0589V11.7502L21.3452 15.6228C21.9165 16.2227 22.866 16.2459 23.4659 15.6746C24.0658 15.1032 24.0889 14.1538 23.5176 13.5539L17.2511 6.97409Z" fill="#0DBD8B"/>
|
||||||
<!-- Generator: bin/sketchtool 1.4 (305) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<title>icons_upload_drop</title>
|
|
||||||
<desc>Created with bin/sketchtool.</desc>
|
|
||||||
<defs></defs>
|
|
||||||
<g id="03-Input" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
|
||||||
<g id="03_05-File-drop" sketch:type="MSArtboardGroup" transform="translate(-570.000000, -368.000000)">
|
|
||||||
<g id="icons_upload_drop" sketch:type="MSLayerGroup" transform="translate(570.000000, 368.000000)">
|
|
||||||
<g id="Rectangle-5-+-Rectangle-6" sketch:type="MSShapeGroup">
|
|
||||||
<path d="M0,4.00812931 C0,1.79450062 1.78537926,0 4.00241155,0 L24.8253683,0 C24.8253683,0 42.2466793,16.8210687 42.2466793,16.8210687 L42.2466793,53.000599 C42.2466793,55.2094072 40.4583762,57 38.2531894,57 L3.99348992,57 C1.78794634,57 0,55.1999609 0,52.9918707 L0,4.00812931 Z" id="Rectangle-5" stroke="#76CFA6"></path>
|
|
||||||
<path d="M40.5848017,19.419576 L29.8354335,19.419576 C26.7387692,19.419576 24.2284269,16.9063989 24.2284269,13.8067771 L24.2284269,4.88501382 L40.5848017,19.419576 Z" id="Rectangle-6-Copy" fill="#FFFFFF"></path>
|
|
||||||
<path d="M42.2466793,18.3870968 L29.539478,18.3870968 C26.4130381,18.3870968 23.8785579,15.8497544 23.8785579,12.7203286 L23.8785579,0" id="Rectangle-6" stroke="#76CFA6"></path>
|
|
||||||
</g>
|
|
||||||
<path d="M31.3419737,32.9284726 C31.701384,32.9284726 32.0607942,32.8000473 32.3359677,32.5414375 C32.8825707,32.0259772 32.8825707,31.1920926 32.3359677,30.6766323 L21.622922,20.6119619 C21.076319,20.0965016 20.187153,20.0982608 19.638678,20.6102026 L8.9125289,30.6607991 C8.36405391,31.1762594 8.36218198,32.0119032 8.90878504,32.5273635 C9.4553881,33.0445831 10.344554,33.0445831 10.893029,32.530882 L19.2399573,24.7092556 L19.2437012,46.487014 C19.2437012,47.2153435 19.874541,47.8064516 20.6476474,47.8064516 C21.4244976,47.8064516 22.0515936,47.2153435 22.0515936,46.487014 L22.0478497,24.7426814 L30.3498517,32.5414375 C30.6231533,32.8000473 30.9825635,32.9284726 31.3419737,32.9284726 L31.3419737,32.9284726 Z" id="Fill-75" fill="#76CFA6" sketch:type="MSShapeGroup"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 802 B |
|
@ -123,6 +123,7 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
|
$space-button-outline-color: rgba(141, 151, 165, 0.2);
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
|
@ -137,9 +138,6 @@ $panel-divider-color: transparent;
|
||||||
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
||||||
$widget-body-bg-color: rgba(141, 151, 165, 0.2);
|
$widget-body-bg-color: rgba(141, 151, 165, 0.2);
|
||||||
|
|
||||||
// event tile lifecycle
|
|
||||||
$event-sending-color: $text-secondary-color;
|
|
||||||
|
|
||||||
// event redaction
|
// event redaction
|
||||||
$event-redacted-fg-color: #606060;
|
$event-redacted-fg-color: #606060;
|
||||||
$event-redacted-border-color: #000000;
|
$event-redacted-border-color: #000000;
|
||||||
|
@ -172,6 +170,9 @@ $button-link-bg-color: transparent;
|
||||||
// Toggle switch
|
// Toggle switch
|
||||||
$togglesw-off-color: $room-highlight-color;
|
$togglesw-off-color: $room-highlight-color;
|
||||||
|
|
||||||
|
$progressbar-fg-color: $accent-color;
|
||||||
|
$progressbar-bg-color: #21262c;
|
||||||
|
|
||||||
$visual-bell-bg-color: #800;
|
$visual-bell-bg-color: #800;
|
||||||
|
|
||||||
$room-warning-bg-color: $header-panel-bg-color;
|
$room-warning-bg-color: $header-panel-bg-color;
|
||||||
|
@ -202,6 +203,10 @@ $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-icon-bg-color: #8e99a4;
|
||||||
|
$message-body-panel-fg-color: $primary-fg-color;
|
||||||
|
|
||||||
// Appearance tab colors
|
// Appearance tab colors
|
||||||
$appearance-tab-border-color: $room-highlight-color;
|
$appearance-tab-border-color: $room-highlight-color;
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,7 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
|
$space-button-outline-color: rgba(141, 151, 165, 0.2);
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
|
@ -132,9 +133,6 @@ $panel-divider-color: $header-panel-border-color;
|
||||||
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
||||||
$widget-body-bg-color: #1A1D23;
|
$widget-body-bg-color: #1A1D23;
|
||||||
|
|
||||||
// event tile lifecycle
|
|
||||||
$event-sending-color: $text-secondary-color;
|
|
||||||
|
|
||||||
// event redaction
|
// event redaction
|
||||||
$event-redacted-fg-color: #606060;
|
$event-redacted-fg-color: #606060;
|
||||||
$event-redacted-border-color: #000000;
|
$event-redacted-border-color: #000000;
|
||||||
|
@ -167,6 +165,9 @@ $button-link-bg-color: transparent;
|
||||||
// Toggle switch
|
// Toggle switch
|
||||||
$togglesw-off-color: $room-highlight-color;
|
$togglesw-off-color: $room-highlight-color;
|
||||||
|
|
||||||
|
$progressbar-fg-color: $accent-color;
|
||||||
|
$progressbar-bg-color: #21262c;
|
||||||
|
|
||||||
$visual-bell-bg-color: #800;
|
$visual-bell-bg-color: #800;
|
||||||
|
|
||||||
$room-warning-bg-color: $header-panel-bg-color;
|
$room-warning-bg-color: $header-panel-bg-color;
|
||||||
|
@ -197,6 +198,10 @@ $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-icon-bg-color: #8e99a4;
|
||||||
|
$message-body-panel-fg-color: $primary-fg-color;
|
||||||
|
|
||||||
// Appearance tab colors
|
// Appearance tab colors
|
||||||
$appearance-tab-border-color: $room-highlight-color;
|
$appearance-tab-border-color: $room-highlight-color;
|
||||||
|
|
||||||
|
|
|
@ -187,6 +187,7 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
|
$space-button-outline-color: #E3E8F0;
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
|
@ -222,8 +223,6 @@ $widget-body-bg-color: #fff;
|
||||||
$yellow-background: #fff8e3;
|
$yellow-background: #fff8e3;
|
||||||
|
|
||||||
// event tile lifecycle
|
// event tile lifecycle
|
||||||
$event-encrypting-color: #abddbc;
|
|
||||||
$event-sending-color: #ddd;
|
|
||||||
$event-notsent-color: #f44;
|
$event-notsent-color: #f44;
|
||||||
|
|
||||||
$event-highlight-fg-color: $warning-color;
|
$event-highlight-fg-color: $warning-color;
|
||||||
|
@ -281,7 +280,8 @@ $togglesw-ball-color: #fff;
|
||||||
$slider-selection-color: $accent-color;
|
$slider-selection-color: $accent-color;
|
||||||
$slider-background-color: #c1c9d6;
|
$slider-background-color: #c1c9d6;
|
||||||
|
|
||||||
$progressbar-color: #000;
|
$progressbar-fg-color: $accent-color;
|
||||||
|
$progressbar-bg-color: rgba(141, 151, 165, 0.2);
|
||||||
|
|
||||||
$room-warning-bg-color: $yellow-background;
|
$room-warning-bg-color: $yellow-background;
|
||||||
|
|
||||||
|
@ -321,6 +321,10 @@ $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-icon-bg-color: #ffffff;
|
||||||
|
$message-body-panel-fg-color: $muted-fg-color;
|
||||||
|
|
||||||
// FontSlider colors
|
// FontSlider colors
|
||||||
$appearance-tab-border-color: $input-darker-bg-color;
|
$appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,6 @@ $groupFilterPanel-bg-color: rgba(232, 232, 232, 0.77);
|
||||||
// used by RoomDirectory permissions
|
// used by RoomDirectory permissions
|
||||||
$plinth-bg-color: $secondary-accent-color;
|
$plinth-bg-color: $secondary-accent-color;
|
||||||
|
|
||||||
// used by RoomDropTarget
|
|
||||||
$droptarget-bg-color: rgba(255,255,255,0.5);
|
|
||||||
|
|
||||||
// used by AddressSelector
|
// used by AddressSelector
|
||||||
$selected-color: $secondary-accent-color;
|
$selected-color: $secondary-accent-color;
|
||||||
|
|
||||||
|
@ -181,6 +178,7 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
|
$space-button-outline-color: #E3E8F0;
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
|
@ -222,8 +220,6 @@ $widget-body-bg-color: #FFF;
|
||||||
$yellow-background: #fff8e3;
|
$yellow-background: #fff8e3;
|
||||||
|
|
||||||
// event tile lifecycle
|
// event tile lifecycle
|
||||||
$event-encrypting-color: #abddbc;
|
|
||||||
$event-sending-color: #ddd;
|
|
||||||
$event-notsent-color: #f44;
|
$event-notsent-color: #f44;
|
||||||
|
|
||||||
$event-highlight-fg-color: $warning-color;
|
$event-highlight-fg-color: $warning-color;
|
||||||
|
@ -281,7 +277,8 @@ $togglesw-ball-color: #fff;
|
||||||
$slider-selection-color: $accent-color;
|
$slider-selection-color: $accent-color;
|
||||||
$slider-background-color: #c1c9d6;
|
$slider-background-color: #c1c9d6;
|
||||||
|
|
||||||
$progressbar-color: #000;
|
$progressbar-fg-color: $accent-color;
|
||||||
|
$progressbar-bg-color: rgba(141, 151, 165, 0.2);
|
||||||
|
|
||||||
$room-warning-bg-color: $yellow-background;
|
$room-warning-bg-color: $yellow-background;
|
||||||
|
|
||||||
|
@ -322,6 +319,10 @@ $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-icon-bg-color: #ffffff;
|
||||||
|
$message-body-panel-fg-color: $muted-fg-color;
|
||||||
|
|
||||||
// FontSlider colors
|
// FontSlider colors
|
||||||
$appearance-tab-border-color: $input-darker-bg-color;
|
$appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { promises: fsp } = fs;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
@ -25,6 +26,8 @@ async function reskindex() {
|
||||||
const header = args.h || args.header;
|
const header = args.h || args.header;
|
||||||
|
|
||||||
const strm = fs.createWriteStream(componentIndexTmp);
|
const strm = fs.createWriteStream(componentIndexTmp);
|
||||||
|
// Wait for the open event to ensure the file descriptor is set
|
||||||
|
await new Promise(resolve => strm.once("open", resolve));
|
||||||
|
|
||||||
if (header) {
|
if (header) {
|
||||||
strm.write(fs.readFileSync(header));
|
strm.write(fs.readFileSync(header));
|
||||||
|
@ -53,14 +56,9 @@ async function reskindex() {
|
||||||
|
|
||||||
strm.write("export {components};\n");
|
strm.write("export {components};\n");
|
||||||
// Ensure the file has been fully written to disk before proceeding
|
// Ensure the file has been fully written to disk before proceeding
|
||||||
|
await util.promisify(fs.fsync)(strm.fd);
|
||||||
await util.promisify(strm.end);
|
await util.promisify(strm.end);
|
||||||
fs.rename(componentIndexTmp, componentIndex, function(err) {
|
await fsp.rename(componentIndexTmp, componentIndex);
|
||||||
if (err) {
|
|
||||||
console.error("Error moving new index into place: " + err);
|
|
||||||
} else {
|
|
||||||
console.log('Reskindex: completed');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects both arrays of file names to be sorted
|
// Expects both arrays of file names to be sorted
|
||||||
|
@ -77,9 +75,17 @@ function filesHaveChanged(files, prevFiles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper since await at the top level is not well supported yet
|
||||||
|
function run() {
|
||||||
|
(async function() {
|
||||||
|
await reskindex();
|
||||||
|
console.log("Reskindex completed");
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
// -w indicates watch mode where any FS events will trigger reskindex
|
// -w indicates watch mode where any FS events will trigger reskindex
|
||||||
if (!args.w) {
|
if (!args.w) {
|
||||||
reskindex();
|
run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,5 +93,5 @@ let watchDebouncer = null;
|
||||||
chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
|
chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
|
||||||
if (path === componentIndex) return;
|
if (path === componentIndex) return;
|
||||||
if (watchDebouncer) clearTimeout(watchDebouncer);
|
if (watchDebouncer) clearTimeout(watchDebouncer);
|
||||||
watchDebouncer = setTimeout(reskindex, 1000);
|
watchDebouncer = setTimeout(run, 1000);
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,27 +14,23 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
import {User} from "matrix-js-sdk/src/models/user";
|
import {User} from "matrix-js-sdk/src/models/user";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
|
||||||
export type ResizeMethod = "crop" | "scale";
|
export type ResizeMethod = "crop" | "scale";
|
||||||
|
|
||||||
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
||||||
export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
|
export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
|
||||||
let url: string;
|
let url: string;
|
||||||
if (member && member.getAvatarUrl) {
|
if (member?.getMxcAvatarUrl()) {
|
||||||
url = member.getAvatarUrl(
|
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
Math.floor(width * window.devicePixelRatio),
|
Math.floor(width * window.devicePixelRatio),
|
||||||
Math.floor(height * window.devicePixelRatio),
|
Math.floor(height * window.devicePixelRatio),
|
||||||
resizeMethod,
|
resizeMethod,
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
@ -47,16 +43,12 @@ export function avatarUrlForMember(member: RoomMember, width: number, height: nu
|
||||||
}
|
}
|
||||||
|
|
||||||
export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
|
export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
|
||||||
const url = getHttpUriForMxc(
|
if (!user.avatarUrl) return null;
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp(
|
||||||
Math.floor(width * window.devicePixelRatio),
|
Math.floor(width * window.devicePixelRatio),
|
||||||
Math.floor(height * window.devicePixelRatio),
|
Math.floor(height * window.devicePixelRatio),
|
||||||
resizeMethod,
|
resizeMethod,
|
||||||
);
|
);
|
||||||
if (!url || url.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidHexColor(color: string): boolean {
|
function isValidHexColor(color: string): boolean {
|
||||||
|
@ -154,15 +146,8 @@ export function getInitialLetter(name: string): string {
|
||||||
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
|
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
|
||||||
if (!room) return null; // null-guard
|
if (!room) return null; // null-guard
|
||||||
|
|
||||||
const explicitRoomAvatar = room.getAvatarUrl(
|
if (room.getMxcAvatarUrl()) {
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
return mediaFromMxc(room.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||||
width,
|
|
||||||
height,
|
|
||||||
resizeMethod,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if (explicitRoomAvatar) {
|
|
||||||
return explicitRoomAvatar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// space rooms cannot be DMs so skip the rest
|
// space rooms cannot be DMs so skip the rest
|
||||||
|
@ -177,14 +162,8 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
|
||||||
// then still try to show any avatar (pref. other member)
|
// then still try to show any avatar (pref. other member)
|
||||||
otherMember = room.getAvatarFallbackMember();
|
otherMember = room.getAvatarFallbackMember();
|
||||||
}
|
}
|
||||||
if (otherMember) {
|
if (otherMember?.getMxcAvatarUrl()) {
|
||||||
return otherMember.getAvatarUrl(
|
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
resizeMethod,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,14 @@ export default abstract class BasePlatform {
|
||||||
hideUpdateToast();
|
hideUpdateToast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if platform supports multi-language
|
||||||
|
* spell-checking, otherwise false.
|
||||||
|
*/
|
||||||
|
supportsMultiLanguageSpellCheck(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the platform supports displaying
|
* Returns true if the platform supports displaying
|
||||||
* notifications, otherwise false.
|
* notifications, otherwise false.
|
||||||
|
@ -240,6 +248,16 @@ export default abstract class BasePlatform {
|
||||||
|
|
||||||
setLanguage(preferredLangs: string[]) {}
|
setLanguage(preferredLangs: string[]) {}
|
||||||
|
|
||||||
|
setSpellCheckLanguages(preferredLangs: string[]) {}
|
||||||
|
|
||||||
|
getSpellCheckLanguages(): Promise<string[]> | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAvailableSpellCheckLanguages(): Promise<string[]> | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected getSSOCallbackUrl(fragmentAfterLogin: string): URL {
|
protected getSSOCallbackUrl(fragmentAfterLogin: string): URL {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.hash = fragmentAfterLogin || "";
|
url.hash = fragmentAfterLogin || "";
|
||||||
|
|
|
@ -609,7 +609,7 @@ export default class CallHandler {
|
||||||
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
||||||
|
|
||||||
const timeUntilTurnCresExpire = MatrixClientPeg.get().getTurnServersExpiry() - Date.now();
|
const timeUntilTurnCresExpire = MatrixClientPeg.get().getTurnServersExpiry() - Date.now();
|
||||||
console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " seconds");
|
console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
|
||||||
const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId);
|
const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId);
|
||||||
|
|
||||||
this.calls.set(roomId, call);
|
this.calls.set(roomId, call);
|
||||||
|
@ -679,6 +679,14 @@ export default class CallHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.getCallForRoom(room.roomId)) {
|
||||||
|
Modal.createTrackedDialog('Call Handler', 'Existing Call with user', ErrorDialog, {
|
||||||
|
title: _t('Already in call'),
|
||||||
|
description: _t("You're already in a call with this person."),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const members = room.getJoinedMembers();
|
const members = room.getJoinedMembers();
|
||||||
if (members.length <= 1) {
|
if (members.length <= 1) {
|
||||||
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
||||||
|
@ -751,6 +759,11 @@ export default class CallHandler {
|
||||||
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
|
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
|
||||||
// the hangup event away)
|
// the hangup event away)
|
||||||
break;
|
break;
|
||||||
|
case 'hangup_all':
|
||||||
|
for (const call of this.calls.values()) {
|
||||||
|
call.hangup(CallErrorCode.UserHangup, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'answer': {
|
case 'answer': {
|
||||||
if (!this.calls.has(payload.room_id)) {
|
if (!this.calls.has(payload.room_id)) {
|
||||||
return; // no call to answer
|
return; // no call to answer
|
||||||
|
|
|
@ -32,6 +32,14 @@ import Spinner from "./components/views/elements/Spinner";
|
||||||
import "blueimp-canvas-to-blob";
|
import "blueimp-canvas-to-blob";
|
||||||
import { Action } from "./dispatcher/actions";
|
import { Action } from "./dispatcher/actions";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
|
import {
|
||||||
|
UploadCanceledPayload,
|
||||||
|
UploadErrorPayload,
|
||||||
|
UploadFinishedPayload,
|
||||||
|
UploadProgressPayload,
|
||||||
|
UploadStartedPayload,
|
||||||
|
} from "./dispatcher/payloads/UploadPayload";
|
||||||
|
import {IUpload} from "./models/IUpload";
|
||||||
|
|
||||||
const MAX_WIDTH = 800;
|
const MAX_WIDTH = 800;
|
||||||
const MAX_HEIGHT = 600;
|
const MAX_HEIGHT = 600;
|
||||||
|
@ -44,15 +52,6 @@ export class UploadCanceledError extends Error {}
|
||||||
|
|
||||||
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||||
|
|
||||||
interface IUpload {
|
|
||||||
fileName: string;
|
|
||||||
roomId: string;
|
|
||||||
total: number;
|
|
||||||
loaded: number;
|
|
||||||
promise: Promise<any>;
|
|
||||||
canceled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMediaConfig {
|
interface IMediaConfig {
|
||||||
"m.upload.size"?: number;
|
"m.upload.size"?: number;
|
||||||
}
|
}
|
||||||
|
@ -478,7 +477,7 @@ export default class ContentMessages {
|
||||||
if (upload) {
|
if (upload) {
|
||||||
upload.canceled = true;
|
upload.canceled = true;
|
||||||
MatrixClientPeg.get().cancelUpload(upload.promise);
|
MatrixClientPeg.get().cancelUpload(upload.promise);
|
||||||
dis.dispatch({action: 'upload_canceled', upload});
|
dis.dispatch<UploadCanceledPayload>({action: Action.UploadCanceled, upload});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,7 +538,7 @@ export default class ContentMessages {
|
||||||
promise: prom,
|
promise: prom,
|
||||||
};
|
};
|
||||||
this.inprogress.push(upload);
|
this.inprogress.push(upload);
|
||||||
dis.dispatch({action: 'upload_started'});
|
dis.dispatch<UploadStartedPayload>({action: Action.UploadStarted, upload});
|
||||||
|
|
||||||
// Focus the composer view
|
// Focus the composer view
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusComposer);
|
||||||
|
@ -547,7 +546,7 @@ export default class ContentMessages {
|
||||||
function onProgress(ev) {
|
function onProgress(ev) {
|
||||||
upload.total = ev.total;
|
upload.total = ev.total;
|
||||||
upload.loaded = ev.loaded;
|
upload.loaded = ev.loaded;
|
||||||
dis.dispatch({action: 'upload_progress', upload: upload});
|
dis.dispatch<UploadProgressPayload>({action: Action.UploadProgress, upload});
|
||||||
}
|
}
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
|
@ -601,9 +600,9 @@ export default class ContentMessages {
|
||||||
if (error && error.http_status === 413) {
|
if (error && error.http_status === 413) {
|
||||||
this.mediaConfig = null;
|
this.mediaConfig = null;
|
||||||
}
|
}
|
||||||
dis.dispatch({action: 'upload_failed', upload, error});
|
dis.dispatch<UploadErrorPayload>({action: Action.UploadFailed, upload, error});
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({action: 'upload_finished', upload});
|
dis.dispatch<UploadFinishedPayload>({action: Action.UploadFinished, upload});
|
||||||
dis.dispatch({action: 'message_sent'});
|
dis.dispatch({action: 'message_sent'});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,10 +32,10 @@ import { AllHtmlEntities } from 'html-entities';
|
||||||
import SettingsStore from './settings/SettingsStore';
|
import SettingsStore from './settings/SettingsStore';
|
||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
|
||||||
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
||||||
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
||||||
import ReplyThread from "./components/views/elements/ReplyThread";
|
import ReplyThread from "./components/views/elements/ReplyThread";
|
||||||
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -181,11 +181,9 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
|
||||||
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
|
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
|
||||||
return { tagName, attribs: {}};
|
return { tagName, attribs: {}};
|
||||||
}
|
}
|
||||||
attribs.src = MatrixClientPeg.get().mxcUrlToHttp(
|
const width = Number(attribs.width) || 800;
|
||||||
attribs.src,
|
const height = Number(attribs.height) || 600;
|
||||||
attribs.width || 800,
|
attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
|
||||||
attribs.height || 600,
|
|
||||||
);
|
|
||||||
return { tagName, attribs };
|
return { tagName, attribs };
|
||||||
},
|
},
|
||||||
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {
|
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {
|
||||||
|
@ -239,6 +237,7 @@ const sanitizeHtmlParams: IExtendedSanitizeOptions = {
|
||||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
|
||||||
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
|
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
|
||||||
|
'details', 'summary',
|
||||||
],
|
],
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
// custom ones first:
|
// custom ones first:
|
||||||
|
|
55
src/Livestream.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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 { ClientWidgetApi } from "matrix-widget-api";
|
||||||
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
|
import SdkConfig from "./SdkConfig";
|
||||||
|
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
|
||||||
|
|
||||||
|
export function getConfigLivestreamUrl() {
|
||||||
|
return SdkConfig.get()["audioStreamUrl"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dummy rtmp URL used to signal that we want a special audio-only stream
|
||||||
|
const AUDIOSTREAM_DUMMY_URL = 'rtmp://audiostream.dummy/';
|
||||||
|
|
||||||
|
async function createLiveStream(roomId: string) {
|
||||||
|
const openIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
|
|
||||||
|
const url = getConfigLivestreamUrl() + "/createStream";
|
||||||
|
|
||||||
|
const response = await window.fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
room_id: roomId,
|
||||||
|
openid_token: openIdToken,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const respBody = await response.json();
|
||||||
|
return respBody['stream_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string) {
|
||||||
|
const streamId = await createLiveStream(roomId);
|
||||||
|
|
||||||
|
await widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, {
|
||||||
|
rtmpStreamKey: AUDIOSTREAM_DUMMY_URL + streamId,
|
||||||
|
});
|
||||||
|
}
|
|
@ -261,7 +261,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHomeserverName(): string {
|
public getHomeserverName(): string {
|
||||||
const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
||||||
if (matches === null || matches.length < 1) {
|
if (matches === null || matches.length < 1) {
|
||||||
throw new Error("Failed to derive homeserver name from user ID!");
|
throw new Error("Failed to derive homeserver name from user ID!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import {SettingLevel} from "./settings/SettingLevel";
|
||||||
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
|
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
|
||||||
import RoomViewStore from "./stores/RoomViewStore";
|
import RoomViewStore from "./stores/RoomViewStore";
|
||||||
import UserActivity from "./UserActivity";
|
import UserActivity from "./UserActivity";
|
||||||
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -150,7 +151,7 @@ export const Notifier = {
|
||||||
// Ideally in here we could use MSC1310 to detect the type of file, and reject it.
|
// Ideally in here we could use MSC1310 to detect the type of file, and reject it.
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: MatrixClientPeg.get().mxcUrlToHttp(content.url),
|
url: mediaFromMxc(content.url).srcHttp,
|
||||||
name: content.name,
|
name: content.name,
|
||||||
type: content.type,
|
type: content.type,
|
||||||
size: content.size,
|
size: content.size,
|
||||||
|
|
|
@ -99,9 +99,9 @@ class Presence {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await MatrixClientPeg.get().setPresence(this.state);
|
await MatrixClientPeg.get().setPresence(this.state);
|
||||||
console.info("Presence: %s", newState);
|
console.info("Presence:", newState);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to set presence: %s", err);
|
console.error("Failed to set presence:", err);
|
||||||
this.state = oldState;
|
this.state = oldState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import MultiInviter from './utils/MultiInviter';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './';
|
import * as sdk from './';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
import InviteDialog, {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||||
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||||
import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
|
import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
|
||||||
|
|
||||||
|
@ -51,9 +51,11 @@ export function showStartChatInviteDialog(initialText) {
|
||||||
|
|
||||||
export function showRoomInviteDialog(roomId) {
|
export function showRoomInviteDialog(roomId) {
|
||||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||||
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId},
|
"Invite Users", "", InviteDialog, {
|
||||||
|
kind: KIND_INVITE,
|
||||||
|
roomId,
|
||||||
|
},
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ class Skinner {
|
||||||
if (!name) throw new Error(`Invalid component name: ${name}`);
|
if (!name) throw new Error(`Invalid component name: ${name}`);
|
||||||
if (this.components === null) {
|
if (this.components === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Attempted to get a component before a skin has been loaded."+
|
`Attempted to get a component (${name}) before a skin has been loaded.`+
|
||||||
" This is probably because either:"+
|
" This is probably because either:"+
|
||||||
" a) Your app has not called sdk.loadSkin(), or"+
|
" a) Your app has not called sdk.loadSkin(), or"+
|
||||||
" b) A component has called getComponent at the root level",
|
" b) A component has called getComponent at the root level",
|
||||||
|
|
|
@ -441,15 +441,14 @@ export const Commands = [
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'invite',
|
command: 'invite',
|
||||||
args: '<user-id>',
|
args: '<user-id> [<reason>]',
|
||||||
description: _td('Invites user with given id to current room'),
|
description: _td('Invites user with given id to current room'),
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
const matches = args.match(/^(\S+)$/);
|
const [address, reason] = args.split(/\s+(.+)/);
|
||||||
if (matches) {
|
if (address) {
|
||||||
// We use a MultiInviter to re-use the invite logic, even though
|
// We use a MultiInviter to re-use the invite logic, even though
|
||||||
// we're only inviting one user.
|
// we're only inviting one user.
|
||||||
const address = matches[1];
|
|
||||||
// If we need an identity server but don't have one, things
|
// If we need an identity server but don't have one, things
|
||||||
// get a bit more complex here, but we try to show something
|
// get a bit more complex here, but we try to show something
|
||||||
// meaningful.
|
// meaningful.
|
||||||
|
@ -490,7 +489,7 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
const inviter = new MultiInviter(roomId);
|
const inviter = new MultiInviter(roomId);
|
||||||
return success(prom.then(() => {
|
return success(prom.then(() => {
|
||||||
return inviter.invite([address]);
|
return inviter.invite([address], reason);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (inviter.getCompletionState(address) !== "invited") {
|
if (inviter.getCompletionState(address) !== "invited") {
|
||||||
throw new Error(inviter.getErrorText(address));
|
throw new Error(inviter.getErrorText(address));
|
||||||
|
|
|
@ -118,25 +118,10 @@ export default class Velociraptor extends React.Component {
|
||||||
domNode.style.visibility = restingStyle.visibility;
|
domNode.style.visibility = restingStyle.visibility;
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
// console.log("enter:",
|
||||||
console.log("enter:",
|
// JSON.stringify(transitionOpts[i-1]),
|
||||||
JSON.stringify(transitionOpts[i-1]),
|
// "->",
|
||||||
"->",
|
// JSON.stringify(restingStyle));
|
||||||
JSON.stringify(restingStyle));
|
|
||||||
*/
|
|
||||||
} else if (node === null) {
|
|
||||||
// Velocity stores data on elements using the jQuery .data()
|
|
||||||
// method, and assumes you'll be using jQuery's .remove() to
|
|
||||||
// remove the element, but we don't use jQuery, so we need to
|
|
||||||
// blow away the element's data explicitly otherwise it will leak.
|
|
||||||
// This uses Velocity's internal jQuery compatible wrapper.
|
|
||||||
// See the bug at
|
|
||||||
// https://github.com/julianshapiro/velocity/issues/300
|
|
||||||
// and the FAQ entry, "Preventing memory leaks when
|
|
||||||
// creating/destroying large numbers of elements"
|
|
||||||
// (https://github.com/julianshapiro/velocity/issues/47)
|
|
||||||
const domNode = ReactDom.findDOMNode(this.nodes[k]);
|
|
||||||
if (domNode) Velocity.Utilities.removeData(domNode);
|
|
||||||
}
|
}
|
||||||
this.nodes[k] = node;
|
this.nodes[k] = node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default class VoipUserMapper {
|
||||||
return results[0].userid;
|
return results[0].userid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getOrCreateVirtualRoomForRoom(roomId: string):Promise<string> {
|
public async getOrCreateVirtualRoomForRoom(roomId: string): Promise<string> {
|
||||||
const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||||
if (!userId) return null;
|
if (!userId) return null;
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export default class VoipUserMapper {
|
||||||
return virtualRoomId;
|
return virtualRoomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public nativeRoomForVirtualRoom(roomId: string):string {
|
public nativeRoomForVirtualRoom(roomId: string): string {
|
||||||
const virtualRoom = MatrixClientPeg.get().getRoom(roomId);
|
const virtualRoom = MatrixClientPeg.get().getRoom(roomId);
|
||||||
if (!virtualRoom) return null;
|
if (!virtualRoom) return null;
|
||||||
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
|
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
|
||||||
|
@ -60,7 +60,7 @@ export default class VoipUserMapper {
|
||||||
return virtualRoomEvent.getContent()['native_room'] || null;
|
return virtualRoomEvent.getContent()['native_room'] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVirtualRoom(room: Room):boolean {
|
public isVirtualRoom(room: Room): boolean {
|
||||||
if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
|
if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
|
||||||
|
|
||||||
if (this.virtualRoomIdCache.has(room.roomId)) return true;
|
if (this.virtualRoomIdCache.has(room.roomId)) return true;
|
||||||
|
@ -79,6 +79,8 @@ export default class VoipUserMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onNewInvitedRoom(invitedRoom: Room) {
|
public async onNewInvitedRoom(invitedRoom: Room) {
|
||||||
|
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
|
||||||
|
|
||||||
const inviterId = invitedRoom.getDMInviter();
|
const inviterId = invitedRoom.getDMInviter();
|
||||||
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||||
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
||||||
|
|
|
@ -19,14 +19,23 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||||
|
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
tooltip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitem
|
// Semantic component for representing a role=menuitem
|
||||||
export const MenuItem: React.FC<IProps> = ({children, label, ...props}) => {
|
export const MenuItem: React.FC<IProps> = ({children, label, tooltip, ...props}) => {
|
||||||
const ariaLabel = props["aria-label"] || label;
|
const ariaLabel = props["aria-label"] || label;
|
||||||
|
|
||||||
|
if (tooltip) {
|
||||||
|
return <AccessibleTooltipButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel} title={tooltip}>
|
||||||
|
{ children }
|
||||||
|
</AccessibleTooltipButton>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel}>
|
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel}>
|
||||||
{ children }
|
{ children }
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {sortBy} from "lodash";
|
||||||
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
||||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||||
import FlairStore from "../stores/FlairStore";
|
import FlairStore from "../stores/FlairStore";
|
||||||
|
import {mediaFromMxc} from "../customisations/Media";
|
||||||
|
|
||||||
const COMMUNITY_REGEX = /\B\+\S*/g;
|
const COMMUNITY_REGEX = /\B\+\S*/g;
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
name={name || groupId}
|
name={name || groupId}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} />
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import classNames from "classnames";
|
||||||
|
|
||||||
import {Key} from "../../Keyboard";
|
import {Key} from "../../Keyboard";
|
||||||
import {Writeable} from "../../@types/common";
|
import {Writeable} from "../../@types/common";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
|
@ -76,6 +77,7 @@ export interface IProps extends IPosition {
|
||||||
hasBackground?: boolean;
|
hasBackground?: boolean;
|
||||||
// whether this context menu should be focus managed. If false it must handle itself
|
// whether this context menu should be focus managed. If false it must handle itself
|
||||||
managed?: boolean;
|
managed?: boolean;
|
||||||
|
wrapperClassName?: string;
|
||||||
|
|
||||||
// Function to be called on menu close
|
// Function to be called on menu close
|
||||||
onFinished();
|
onFinished();
|
||||||
|
@ -90,6 +92,7 @@ interface IState {
|
||||||
// Generic ContextMenu Portal wrapper
|
// Generic ContextMenu Portal wrapper
|
||||||
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
||||||
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
||||||
|
@replaceableComponent("structures.ContextMenu")
|
||||||
export class ContextMenu extends React.PureComponent<IProps, IState> {
|
export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
private initialFocus: HTMLElement;
|
private initialFocus: HTMLElement;
|
||||||
|
|
||||||
|
@ -365,7 +368,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="mx_ContextualMenu_wrapper"
|
className={classNames("mx_ContextualMenu_wrapper", this.props.wrapperClassName)}
|
||||||
style={{...position, ...wrapperStyle}}
|
style={{...position, ...wrapperStyle}}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
onContextMenu={this.onContextMenuPreventBubbling}
|
onContextMenu={this.onContextMenuPreventBubbling}
|
||||||
|
@ -466,6 +469,7 @@ export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<
|
||||||
return [isOpen, button, open, close, setIsOpen];
|
return [isOpen, button, open, close, setIsOpen];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@replaceableComponent("structures.LegacyContextMenu")
|
||||||
export default class LegacyContextMenu extends ContextMenu {
|
export default class LegacyContextMenu extends ContextMenu {
|
||||||
render() {
|
render() {
|
||||||
return this.renderMenu(false);
|
return this.renderMenu(false);
|
||||||
|
|
|
@ -21,7 +21,9 @@ import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.CustomRoomTagPanel")
|
||||||
class CustomRoomTagPanel extends React.Component {
|
class CustomRoomTagPanel extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
|
@ -16,8 +16,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
|
|
|
@ -26,10 +26,12 @@ import { _t } from '../../languageHandler';
|
||||||
import BaseCard from "../views/right_panel/BaseCard";
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||||
import DesktopBuildsNotice, {WarningKind} from "../views/elements/DesktopBuildsNotice";
|
import DesktopBuildsNotice, {WarningKind} from "../views/elements/DesktopBuildsNotice";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the filtered file using a TimelinePanel
|
* Component which shows the filtered file using a TimelinePanel
|
||||||
*/
|
*/
|
||||||
|
@replaceableComponent("structures.FilePanel")
|
||||||
class FilePanel extends React.Component {
|
class FilePanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
|
|
|
@ -16,7 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.GenericErrorPage")
|
||||||
export default class GenericErrorPage extends React.PureComponent {
|
export default class GenericErrorPage extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.object.isRequired, // jsx for title
|
title: PropTypes.object.isRequired, // jsx for title
|
||||||
|
|
|
@ -30,7 +30,9 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import UserTagTile from "../views/elements/UserTagTile";
|
import UserTagTile from "../views/elements/UserTagTile";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.GroupFilterPanel")
|
||||||
class GroupFilterPanel extends React.Component {
|
class GroupFilterPanel extends React.Component {
|
||||||
static contextType = MatrixClientContext;
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,8 @@ import {Group} from "matrix-js-sdk";
|
||||||
import {allSettled, sleep} from "../../utils/promise";
|
import {allSettled, sleep} from "../../utils/promise";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
const LONG_DESC_PLACEHOLDER = _td(
|
const LONG_DESC_PLACEHOLDER = _td(
|
||||||
`<h1>HTML for your community's page</h1>
|
`<h1>HTML for your community's page</h1>
|
||||||
|
@ -367,8 +369,7 @@ class FeaturedUser extends React.Component {
|
||||||
|
|
||||||
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
|
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
|
||||||
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
||||||
const httpUrl = MatrixClientPeg.get()
|
const httpUrl = mediaFromMxc(this.props.summaryInfo.avatar_url).getSquareThumbnailHttp(64);
|
||||||
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
|
|
||||||
|
|
||||||
const deleteButton = this.props.editing ?
|
const deleteButton = this.props.editing ?
|
||||||
<img
|
<img
|
||||||
|
@ -391,6 +392,7 @@ class FeaturedUser extends React.Component {
|
||||||
const GROUP_JOINPOLICY_OPEN = "open";
|
const GROUP_JOINPOLICY_OPEN = "open";
|
||||||
const GROUP_JOINPOLICY_INVITE = "invite";
|
const GROUP_JOINPOLICY_INVITE = "invite";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.GroupView")
|
||||||
export default class GroupView extends React.Component {
|
export default class GroupView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
groupId: PropTypes.string.isRequired,
|
groupId: PropTypes.string.isRequired,
|
||||||
|
@ -979,10 +981,9 @@ export default class GroupView extends React.Component {
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const httpInviterAvatar = this.state.inviterProfile ?
|
const httpInviterAvatar = this.state.inviterProfile
|
||||||
this._matrixClient.mxcUrlToHttp(
|
? mediaFromMxc(this.state.inviterProfile.avatarUrl).getSquareThumbnailHttp(36)
|
||||||
this.state.inviterProfile.avatarUrl, 36, 36,
|
: null;
|
||||||
) : null;
|
|
||||||
|
|
||||||
const inviter = group.inviter || {};
|
const inviter = group.inviter || {};
|
||||||
let inviterName = inviter.userId;
|
let inviterName = inviter.userId;
|
||||||
|
|
|
@ -22,11 +22,13 @@ import {
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import { HostSignupStore } from "../../stores/HostSignupStore";
|
import { HostSignupStore } from "../../stores/HostSignupStore";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
interface IState {}
|
interface IState {}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.HostSignupAction")
|
||||||
export default class HostSignupAction extends React.PureComponent<IProps, IState> {
|
export default class HostSignupAction extends React.PureComponent<IProps, IState> {
|
||||||
private openDialog = async () => {
|
private openDialog = async () => {
|
||||||
await HostSignupStore.instance.setHostSignupActive(true);
|
await HostSignupStore.instance.setHostSignupActive(true);
|
||||||
|
|
|
@ -17,7 +17,9 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.IndicatorScrollbar")
|
||||||
export default class IndicatorScrollbar extends React.Component {
|
export default class IndicatorScrollbar extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
|
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
|
||||||
|
|
|
@ -22,9 +22,11 @@ import PropTypes from 'prop-types';
|
||||||
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
|
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
|
||||||
|
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||||
|
|
||||||
|
@replaceableComponent("structures.InteractiveAuthComponent")
|
||||||
export default class InteractiveAuthComponent extends React.Component {
|
export default class InteractiveAuthComponent extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// matrix client to use for UI auth requests
|
// matrix client to use for UI auth requests
|
||||||
|
|
|
@ -36,10 +36,11 @@ import {Key} from "../../Keyboard";
|
||||||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|
||||||
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
||||||
import LeftPanelWidget from "./LeftPanelWidget";
|
import LeftPanelWidget from "./LeftPanelWidget";
|
||||||
import SpacePanel from "../views/spaces/SpacePanel";
|
import SpacePanel from "../views/spaces/SpacePanel";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -60,6 +61,7 @@ const cssClasses = [
|
||||||
"mx_RoomSublist_showNButton",
|
"mx_RoomSublist_showNButton",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@replaceableComponent("structures.LeftPanel")
|
||||||
export default class LeftPanel extends React.Component<IProps, IState> {
|
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
|
private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
|
||||||
private groupFilterPanelWatcherRef: string;
|
private groupFilterPanelWatcherRef: string;
|
||||||
|
@ -119,7 +121,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||||
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
|
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
|
||||||
if (settingBgMxc) {
|
if (settingBgMxc) {
|
||||||
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(settingBgMxc, avatarSize, avatarSize);
|
avatarUrl = mediaFromMxc(settingBgMxc).getSquareThumbnailHttp(avatarSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarUrlProp = `url(${avatarUrl})`;
|
const avatarUrlProp = `url(${avatarUrl})`;
|
||||||
|
|
|
@ -55,6 +55,8 @@ import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
||||||
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
||||||
|
import { IOpts } from "../../createRoom";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -91,11 +93,14 @@ interface IProps {
|
||||||
currentGroupId?: string;
|
currentGroupId?: string;
|
||||||
currentGroupIsNew?: boolean;
|
currentGroupIsNew?: boolean;
|
||||||
justRegistered?: boolean;
|
justRegistered?: boolean;
|
||||||
|
roomJustCreatedOpts?: IOpts;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUsageLimit {
|
interface IUsageLimit {
|
||||||
|
// "hs_disabled" is NOT a specced string, but is used in Synapse
|
||||||
|
// This is tracked over at https://github.com/matrix-org/synapse/issues/9237
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
limit_type: "monthly_active_user" | string;
|
limit_type: "monthly_active_user" | "hs_disabled" | string;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
admin_contact?: string;
|
admin_contact?: string;
|
||||||
}
|
}
|
||||||
|
@ -103,6 +108,8 @@ interface IUsageLimit {
|
||||||
interface IState {
|
interface IState {
|
||||||
syncErrorData?: {
|
syncErrorData?: {
|
||||||
error: {
|
error: {
|
||||||
|
// This is not specced, but used in Synapse. See
|
||||||
|
// https://github.com/matrix-org/synapse/issues/9237#issuecomment-768238922
|
||||||
data: IUsageLimit;
|
data: IUsageLimit;
|
||||||
errcode: string;
|
errcode: string;
|
||||||
};
|
};
|
||||||
|
@ -122,6 +129,7 @@ interface IState {
|
||||||
*
|
*
|
||||||
* Components mounted below us can access the matrix client via the react context.
|
* Components mounted below us can access the matrix client via the react context.
|
||||||
*/
|
*/
|
||||||
|
@replaceableComponent("structures.LoggedInView")
|
||||||
class LoggedInView extends React.Component<IProps, IState> {
|
class LoggedInView extends React.Component<IProps, IState> {
|
||||||
static displayName = 'LoggedInView';
|
static displayName = 'LoggedInView';
|
||||||
|
|
||||||
|
@ -221,14 +229,21 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
let size;
|
let size;
|
||||||
let collapsed;
|
let collapsed;
|
||||||
const collapseConfig: ICollapseConfig = {
|
const collapseConfig: ICollapseConfig = {
|
||||||
toggleSize: 260 - 50,
|
// TODO: the space panel currently does not have a fixed width,
|
||||||
|
// just the headers at each level have a max-width of 150px
|
||||||
|
// Taking 222px for the space panel for now,
|
||||||
|
// so this will look slightly off for now,
|
||||||
|
// depending on the depth of your space tree.
|
||||||
|
// To fix this, we'll need to turn toggleSize
|
||||||
|
// into a callback so it can be measured when starting the resize operation
|
||||||
|
toggleSize: 222 + 68,
|
||||||
onCollapsed: (_collapsed) => {
|
onCollapsed: (_collapsed) => {
|
||||||
collapsed = _collapsed;
|
collapsed = _collapsed;
|
||||||
if (_collapsed) {
|
if (_collapsed) {
|
||||||
dis.dispatch({action: "hide_left_panel"}, true);
|
dis.dispatch({action: "hide_left_panel"});
|
||||||
window.localStorage.setItem("mx_lhs_size", '0');
|
window.localStorage.setItem("mx_lhs_size", '0');
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({action: "show_left_panel"}, true);
|
dis.dispatch({action: "show_left_panel"});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onResized: (_size) => {
|
onResized: (_size) => {
|
||||||
|
@ -242,6 +257,9 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size);
|
if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size);
|
||||||
this.props.resizeNotifier.stopResizing();
|
this.props.resizeNotifier.stopResizing();
|
||||||
},
|
},
|
||||||
|
isItemCollapsed: domNode => {
|
||||||
|
return domNode.classList.contains("mx_LeftPanel_minimized");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig);
|
const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig);
|
||||||
resizer.setClassNames({
|
resizer.setClassNames({
|
||||||
|
@ -619,6 +637,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
viaServers={this.props.viaServers}
|
viaServers={this.props.viaServers}
|
||||||
key={this.props.currentRoomId || 'roomview'}
|
key={this.props.currentRoomId || 'roomview'}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
justCreatedOpts={this.props.roomJustCreatedOpts}
|
||||||
/>;
|
/>;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Resizable } from 're-resizable';
|
import { Resizable } from 're-resizable';
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.MainSplit")
|
||||||
export default class MainSplit extends React.Component {
|
export default class MainSplit extends React.Component {
|
||||||
_onResizeStart = () => {
|
_onResizeStart = () => {
|
||||||
this.props.resizeNotifier.startResizing();
|
this.props.resizeNotifier.startResizing();
|
||||||
|
|
|
@ -48,7 +48,7 @@ import * as Lifecycle from '../../Lifecycle';
|
||||||
import '../../stores/LifecycleStore';
|
import '../../stores/LifecycleStore';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
|
|
||||||
import createRoom from "../../createRoom";
|
import createRoom, {IOpts} from "../../createRoom";
|
||||||
import {_t, _td, getCurrentLanguage} from '../../languageHandler';
|
import {_t, _td, getCurrentLanguage} from '../../languageHandler';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import ThemeController from "../../settings/controllers/ThemeController";
|
import ThemeController from "../../settings/controllers/ThemeController";
|
||||||
|
@ -82,6 +82,9 @@ import {UIFeature} from "../../settings/UIFeature";
|
||||||
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
||||||
import DialPadModal from "../views/voip/DialPadModal";
|
import DialPadModal from "../views/voip/DialPadModal";
|
||||||
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
||||||
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
|
import SpaceRoomDirectory from "./SpaceRoomDirectory";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -144,6 +147,8 @@ interface IRoomInfo {
|
||||||
oob_data?: object;
|
oob_data?: object;
|
||||||
via_servers?: string[];
|
via_servers?: string[];
|
||||||
threepid_invite?: IThreepidInvite;
|
threepid_invite?: IThreepidInvite;
|
||||||
|
|
||||||
|
justCreatedOpts?: IOpts;
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
@ -201,8 +206,10 @@ interface IState {
|
||||||
viaServers?: string[];
|
viaServers?: string[];
|
||||||
pendingInitialSync?: boolean;
|
pendingInitialSync?: boolean;
|
||||||
justRegistered?: boolean;
|
justRegistered?: boolean;
|
||||||
|
roomJustCreatedOpts?: IOpts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.MatrixChat")
|
||||||
export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
static displayName = "MatrixChat";
|
static displayName = "MatrixChat";
|
||||||
|
|
||||||
|
@ -575,6 +582,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'logout':
|
case 'logout':
|
||||||
|
dis.dispatch({action: "hangup_all"});
|
||||||
Lifecycle.logout();
|
Lifecycle.logout();
|
||||||
break;
|
break;
|
||||||
case 'require_registration':
|
case 'require_registration':
|
||||||
|
@ -688,10 +696,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Action.ViewRoomDirectory: {
|
case Action.ViewRoomDirectory: {
|
||||||
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
if (SpaceStore.instance.activeSpace) {
|
||||||
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
Modal.createTrackedDialog("Space room directory", "", SpaceRoomDirectory, {
|
||||||
initialText: payload.initialText,
|
space: SpaceStore.instance.activeSpace,
|
||||||
}, 'mx_RoomDirectory_dialogWrapper', false, true);
|
initialText: payload.initialText,
|
||||||
|
}, "mx_SpaceRoomDirectory_dialogWrapper", false, true);
|
||||||
|
} else {
|
||||||
|
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
||||||
|
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
||||||
|
initialText: payload.initialText,
|
||||||
|
}, 'mx_RoomDirectory_dialogWrapper', false, true);
|
||||||
|
}
|
||||||
|
|
||||||
// View the welcome or home page if we need something to look at
|
// View the welcome or home page if we need something to look at
|
||||||
this.viewSomethingBehindModal();
|
this.viewSomethingBehindModal();
|
||||||
|
@ -922,6 +937,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
viaServers: roomInfo.via_servers,
|
viaServers: roomInfo.via_servers,
|
||||||
ready: true,
|
ready: true,
|
||||||
|
roomJustCreatedOpts: roomInfo.justCreatedOpts,
|
||||||
}, () => {
|
}, () => {
|
||||||
this.notifyNewScreen('room/' + presentedId, replaceLast);
|
this.notifyNewScreen('room/' + presentedId, replaceLast);
|
||||||
});
|
});
|
||||||
|
@ -1068,6 +1084,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();
|
||||||
// Show a warning if there are additional complications.
|
// Show a warning if there are additional complications.
|
||||||
const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', '');
|
const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', '');
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
@ -1077,7 +1094,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
warnings.push((
|
warnings.push((
|
||||||
<span className="warning" key="non_public_warning">
|
<span className="warning" key="non_public_warning">
|
||||||
{' '/* Whitespace, otherwise the sentences get smashed together */ }
|
{' '/* Whitespace, otherwise the sentences get smashed together */ }
|
||||||
{ _t("This room is not public. You will not be able to rejoin without an invite.") }
|
{ isSpace
|
||||||
|
? _t("This space is not public. You will not be able to rejoin without an invite.")
|
||||||
|
: _t("This room is not public. You will not be able to rejoin without an invite.") }
|
||||||
</span>
|
</span>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -1090,11 +1109,14 @@ 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);
|
||||||
|
|
||||||
Modal.createTrackedDialog('Leave room', '', QuestionDialog, {
|
const isSpace = roomToLeave?.isSpaceRoom();
|
||||||
title: _t("Leave room"),
|
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
|
||||||
|
title: isSpace ? _t("Leave space") : _t("Leave room"),
|
||||||
description: (
|
description: (
|
||||||
<span>
|
<span>
|
||||||
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
{ isSpace
|
||||||
|
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {spaceName: roomToLeave.name})
|
||||||
|
: _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
||||||
{ warnings }
|
{ warnings }
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
@ -1108,6 +1130,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
d.finally(() => modal.close());
|
d.finally(() => modal.close());
|
||||||
|
dis.dispatch({
|
||||||
|
action: "after_leave_room",
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {textForEvent} from "../../TextForEvent";
|
||||||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||||
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";
|
||||||
|
|
||||||
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'];
|
||||||
|
@ -66,6 +67,7 @@ const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType()
|
||||||
|
|
||||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||||
*/
|
*/
|
||||||
|
@replaceableComponent("structures.MessagePanel")
|
||||||
export default class MessagePanel extends React.Component {
|
export default class MessagePanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// true to give the component a 'display: none' style.
|
// true to give the component a 'display: none' style.
|
||||||
|
@ -498,6 +500,9 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
let prevEvent = null; // the last event we showed
|
let prevEvent = null; // the last event we showed
|
||||||
|
|
||||||
|
// Note: the EventTile might still render a "sent/sending receipt" independent of
|
||||||
|
// this information. When not providing read receipt information, the tile is likely
|
||||||
|
// to assume that sent receipts are to be shown more often.
|
||||||
this._readReceiptsByEvent = {};
|
this._readReceiptsByEvent = {};
|
||||||
if (this.props.showReadReceipts) {
|
if (this.props.showReadReceipts) {
|
||||||
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
|
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
|
||||||
|
@ -534,10 +539,17 @@ export default class MessagePanel extends React.Component {
|
||||||
const nextEvent = i < this.props.events.length - 1
|
const nextEvent = i < this.props.events.length - 1
|
||||||
? this.props.events[i + 1]
|
? this.props.events[i + 1]
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// The next event with tile is used to to determine the 'last successful' flag
|
||||||
|
// when rendering the tile. The shouldShowEvent function is pretty quick at what
|
||||||
|
// it does, so this should have no significant cost even when a room is used for
|
||||||
|
// not-chat purposes.
|
||||||
|
const nextTile = this.props.events.slice(i + 1).find(e => this._shouldShowEvent(e));
|
||||||
|
|
||||||
// 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));
|
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextTile));
|
||||||
prevEvent = mxEv;
|
prevEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +565,7 @@ export default class MessagePanel extends React.Component {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTilesForEvent(prevEvent, mxEv, last, nextEvent) {
|
_getTilesForEvent(prevEvent, mxEv, last, 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');
|
||||||
|
@ -595,6 +607,30 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
const readReceipts = this._readReceiptsByEvent[eventId];
|
const readReceipts = this._readReceiptsByEvent[eventId];
|
||||||
|
|
||||||
|
let isLastSuccessful = false;
|
||||||
|
const isSentState = s => !s || s === 'sent';
|
||||||
|
const isSent = isSentState(mxEv.getAssociatedStatus());
|
||||||
|
const hasNextEvent = nextEvent && this._shouldShowEvent(nextEvent);
|
||||||
|
if (!hasNextEvent && isSent) {
|
||||||
|
isLastSuccessful = true;
|
||||||
|
} else if (hasNextEvent && isSent && !isSentState(nextEvent.getAssociatedStatus())) {
|
||||||
|
isLastSuccessful = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a bit nuanced, but if our next event is hidden but a future event is not
|
||||||
|
// hidden then we're not the last successful.
|
||||||
|
if (
|
||||||
|
nextEventWithTile &&
|
||||||
|
nextEventWithTile !== nextEvent &&
|
||||||
|
isSentState(nextEventWithTile.getAssociatedStatus())
|
||||||
|
) {
|
||||||
|
isLastSuccessful = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only want to consider "last successful" if the event is sent by us, otherwise of course
|
||||||
|
// it's successful: we received it.
|
||||||
|
isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId();
|
||||||
|
|
||||||
// use txnId as key if available so that we don't remount during sending
|
// use txnId as key if available so that we don't remount during sending
|
||||||
ret.push(
|
ret.push(
|
||||||
<li
|
<li
|
||||||
|
@ -620,6 +656,7 @@ export default class MessagePanel extends React.Component {
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
last={last}
|
last={last}
|
||||||
lastInSection={willWantDateSeparator}
|
lastInSection={willWantDateSeparator}
|
||||||
|
lastSuccessful={isLastSuccessful}
|
||||||
isSelectedEvent={highlight}
|
isSelectedEvent={highlight}
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
|
|
|
@ -24,7 +24,9 @@ import dis from '../../dispatcher/dispatcher';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
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";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.MyGroups")
|
||||||
export default class MyGroups extends React.Component {
|
export default class MyGroups extends React.Component {
|
||||||
static contextType = MatrixClientContext;
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import * as React from "react";
|
||||||
import { ComponentClass } from "../../@types/common";
|
import { ComponentClass } from "../../@types/common";
|
||||||
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
|
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
|
||||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
}
|
}
|
||||||
|
@ -26,6 +27,7 @@ interface IState {
|
||||||
toasts: ComponentClass[],
|
toasts: ComponentClass[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.NonUrgentToastContainer")
|
||||||
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
||||||
public constructor(props, context) {
|
public constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
|
@ -23,10 +23,12 @@ import { _t } from '../../languageHandler';
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import BaseCard from "../views/right_panel/BaseCard";
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the global notification list using a TimelinePanel
|
* Component which shows the global notification list using a TimelinePanel
|
||||||
*/
|
*/
|
||||||
|
@replaceableComponent("structures.NotificationPanel")
|
||||||
class NotificationPanel extends React.Component {
|
class NotificationPanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -24,13 +24,19 @@ import dis from '../../dispatcher/dispatcher';
|
||||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
import RateLimitedFunc from '../../ratelimitedfunc';
|
||||||
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||||
import GroupStore from '../../stores/GroupStore';
|
import GroupStore from '../../stores/GroupStore';
|
||||||
import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
|
import {
|
||||||
|
RightPanelPhases,
|
||||||
|
RIGHT_PANEL_PHASES_NO_ARGS,
|
||||||
|
RIGHT_PANEL_SPACE_PHASES,
|
||||||
|
} from "../../stores/RightPanelStorePhases";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import {Action} from "../../dispatcher/actions";
|
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";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.RightPanel")
|
||||||
export default class RightPanel extends React.Component {
|
export default class RightPanel extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
|
@ -79,6 +85,8 @@ 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)) {
|
||||||
|
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
|
||||||
// from its props and some from a store, except if the contents of the store changes
|
// from its props and some from a store, except if the contents of the store changes
|
||||||
|
@ -99,9 +107,8 @@ export default class RightPanel extends React.Component {
|
||||||
return rps.roomPanelPhase;
|
return rps.roomPanelPhase;
|
||||||
}
|
}
|
||||||
return RightPanelPhases.RoomMemberInfo;
|
return RightPanelPhases.RoomMemberInfo;
|
||||||
} else {
|
|
||||||
return rps.roomPanelPhase;
|
|
||||||
}
|
}
|
||||||
|
return rps.roomPanelPhase;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -181,6 +188,7 @@ export default class RightPanel extends React.Component {
|
||||||
verificationRequest: payload.verificationRequest,
|
verificationRequest: payload.verificationRequest,
|
||||||
verificationRequestPromise: payload.verificationRequestPromise,
|
verificationRequestPromise: payload.verificationRequestPromise,
|
||||||
widgetId: payload.widgetId,
|
widgetId: payload.widgetId,
|
||||||
|
space: payload.space,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,6 +240,13 @@ export default class RightPanel extends React.Component {
|
||||||
panel = <MemberList roomId={roomId} key={roomId} onClose={this.onClose} />;
|
panel = <MemberList roomId={roomId} key={roomId} onClose={this.onClose} />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case RightPanelPhases.SpaceMemberList:
|
||||||
|
panel = <MemberList
|
||||||
|
roomId={this.state.space ? this.state.space.roomId : roomId}
|
||||||
|
key={this.state.space ? this.state.space.roomId : roomId}
|
||||||
|
onClose={this.onClose}
|
||||||
|
/>;
|
||||||
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.GroupMemberList:
|
case RightPanelPhases.GroupMemberList:
|
||||||
if (this.props.groupId) {
|
if (this.props.groupId) {
|
||||||
|
@ -244,10 +259,11 @@ export default class RightPanel extends React.Component {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.RoomMemberInfo:
|
case RightPanelPhases.RoomMemberInfo:
|
||||||
|
case RightPanelPhases.SpaceMemberInfo:
|
||||||
case RightPanelPhases.EncryptionPanel:
|
case RightPanelPhases.EncryptionPanel:
|
||||||
panel = <UserInfo
|
panel = <UserInfo
|
||||||
user={this.state.member}
|
user={this.state.member}
|
||||||
room={this.props.room}
|
room={this.state.phase === RightPanelPhases.SpaceMemberInfo ? this.state.space : this.props.room}
|
||||||
key={roomId || this.state.member.userId}
|
key={roomId || this.state.member.userId}
|
||||||
onClose={this.onClose}
|
onClose={this.onClose}
|
||||||
phase={this.state.phase}
|
phase={this.state.phase}
|
||||||
|
@ -257,6 +273,7 @@ export default class RightPanel extends React.Component {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.Room3pidMemberInfo:
|
case RightPanelPhases.Room3pidMemberInfo:
|
||||||
|
case RightPanelPhases.Space3pidMemberInfo:
|
||||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />;
|
panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,14 @@ import { _t } from '../../languageHandler';
|
||||||
import SdkConfig from '../../SdkConfig';
|
import SdkConfig from '../../SdkConfig';
|
||||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||||
import Analytics from '../../Analytics';
|
import Analytics from '../../Analytics';
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
||||||
import GroupStore from "../../stores/GroupStore";
|
import GroupStore from "../../stores/GroupStore";
|
||||||
import FlairStore from "../../stores/FlairStore";
|
import FlairStore from "../../stores/FlairStore";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
import CountlyAnalytics from "../../CountlyAnalytics";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 800;
|
const MAX_TOPIC_LENGTH = 800;
|
||||||
|
@ -42,6 +43,7 @@ function track(action) {
|
||||||
Analytics.trackEvent('RoomDirectory', action);
|
Analytics.trackEvent('RoomDirectory', action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.RoomDirectory")
|
||||||
export default class RoomDirectory extends React.Component {
|
export default class RoomDirectory extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
initialText: PropTypes.string,
|
initialText: PropTypes.string,
|
||||||
|
@ -519,10 +521,9 @@ export default class RoomDirectory extends React.Component {
|
||||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||||
}
|
}
|
||||||
topic = linkifyAndSanitizeHtml(topic);
|
topic = linkifyAndSanitizeHtml(topic);
|
||||||
const avatarUrl = getHttpUriForMxc(
|
let avatarUrl = null;
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
|
||||||
room.avatar_url, 32, 32, "crop",
|
|
||||||
);
|
|
||||||
return [
|
return [
|
||||||
<div key={ `${room.room_id}_avatar` }
|
<div key={ `${room.room_id}_avatar` }
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -37,6 +38,7 @@ interface IState {
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.RoomSearch")
|
||||||
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
private dispatcherRef: string;
|
private dispatcherRef: string;
|
||||||
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Resend from '../../Resend';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
||||||
import {Action} from "../../dispatcher/actions";
|
import {Action} from "../../dispatcher/actions";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
const STATUS_BAR_EXPANDED = 1;
|
const STATUS_BAR_EXPANDED = 1;
|
||||||
|
@ -35,6 +36,7 @@ function getUnsentMessages(room) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.RoomStatusBar")
|
||||||
export default class RoomStatusBar extends React.Component {
|
export default class RoomStatusBar extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// the room this statusbar is representing.
|
// the room this statusbar is representing.
|
||||||
|
@ -195,6 +197,10 @@ export default class RoomStatusBar extends React.Component {
|
||||||
"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.",
|
||||||
),
|
),
|
||||||
|
'hs_disabled': _td(
|
||||||
|
"Your message wasn't sent because this homeserver has been blocked by it's administrator. " +
|
||||||
|
"Please <a>contact your service administrator</a> to continue using the service.",
|
||||||
|
),
|
||||||
'': _td(
|
'': _td(
|
||||||
"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.",
|
||||||
|
|
|
@ -80,6 +80,9 @@ import { showToast as showNotificationsToast } from "../../toasts/DesktopNotific
|
||||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
||||||
import { objectHasDiff } from "../../utils/objects";
|
import { objectHasDiff } from "../../utils/objects";
|
||||||
|
import SpaceRoomView from "./SpaceRoomView";
|
||||||
|
import { IOpts } from "../../createRoom";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
let debuglog = function(msg: string) {};
|
let debuglog = function(msg: string) {};
|
||||||
|
@ -114,6 +117,7 @@ interface IProps {
|
||||||
|
|
||||||
autoJoin?: boolean;
|
autoJoin?: boolean;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
|
justCreatedOpts?: IOpts;
|
||||||
|
|
||||||
// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
|
// Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU)
|
||||||
onRegistered?(credentials: IMatrixClientCreds): void;
|
onRegistered?(credentials: IMatrixClientCreds): void;
|
||||||
|
@ -189,8 +193,10 @@ export interface IState {
|
||||||
rejecting?: boolean;
|
rejecting?: boolean;
|
||||||
rejectError?: Error;
|
rejectError?: Error;
|
||||||
hasPinnedWidgets?: boolean;
|
hasPinnedWidgets?: boolean;
|
||||||
|
dragCounter: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.RoomView")
|
||||||
export default class RoomView extends React.Component<IProps, IState> {
|
export default class RoomView extends React.Component<IProps, IState> {
|
||||||
private readonly dispatcherRef: string;
|
private readonly dispatcherRef: string;
|
||||||
private readonly roomStoreToken: EventSubscription;
|
private readonly roomStoreToken: EventSubscription;
|
||||||
|
@ -239,6 +245,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
canReply: false,
|
canReply: false,
|
||||||
layout: SettingsStore.getValue("layout"),
|
layout: SettingsStore.getValue("layout"),
|
||||||
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
|
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
|
||||||
|
dragCounter: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
@ -532,8 +539,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
if (!roomView.ondrop) {
|
if (!roomView.ondrop) {
|
||||||
roomView.addEventListener('drop', this.onDrop);
|
roomView.addEventListener('drop', this.onDrop);
|
||||||
roomView.addEventListener('dragover', this.onDragOver);
|
roomView.addEventListener('dragover', this.onDragOver);
|
||||||
roomView.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
roomView.addEventListener('dragenter', this.onDragEnter);
|
||||||
roomView.addEventListener('dragend', this.onDragLeaveOrEnd);
|
roomView.addEventListener('dragleave', this.onDragLeave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,8 +584,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
const roomView = this.roomView.current;
|
const roomView = this.roomView.current;
|
||||||
roomView.removeEventListener('drop', this.onDrop);
|
roomView.removeEventListener('drop', this.onDrop);
|
||||||
roomView.removeEventListener('dragover', this.onDragOver);
|
roomView.removeEventListener('dragover', this.onDragOver);
|
||||||
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
roomView.removeEventListener('dragenter', this.onDragEnter);
|
||||||
roomView.removeEventListener('dragend', this.onDragLeaveOrEnd);
|
roomView.removeEventListener('dragleave', this.onDragLeave);
|
||||||
}
|
}
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
|
@ -706,9 +713,9 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
[payload.file], this.state.room.roomId, this.context);
|
[payload.file], this.state.room.roomId, this.context);
|
||||||
break;
|
break;
|
||||||
case 'notifier_enabled':
|
case 'notifier_enabled':
|
||||||
case 'upload_started':
|
case Action.UploadStarted:
|
||||||
case 'upload_finished':
|
case Action.UploadFinished:
|
||||||
case 'upload_canceled':
|
case Action.UploadCanceled:
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
break;
|
break;
|
||||||
case 'call_state': {
|
case 'call_state': {
|
||||||
|
@ -1138,6 +1145,31 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
this.updateTopUnreadMessagesBar();
|
this.updateTopUnreadMessagesBar();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onDragEnter = ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
dragCounter: this.state.dragCounter + 1,
|
||||||
|
draggingFile: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onDragLeave = ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
dragCounter: this.state.dragCounter - 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.state.dragCounter === 0) {
|
||||||
|
this.setState({
|
||||||
|
draggingFile: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onDragOver = ev => {
|
private onDragOver = ev => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -1145,7 +1177,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
ev.dataTransfer.dropEffect = 'none';
|
ev.dataTransfer.dropEffect = 'none';
|
||||||
|
|
||||||
if (ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file")) {
|
if (ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file")) {
|
||||||
this.setState({ draggingFile: true });
|
|
||||||
ev.dataTransfer.dropEffect = 'copy';
|
ev.dataTransfer.dropEffect = 'copy';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1156,14 +1187,12 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
ContentMessages.sharedInstance().sendContentListToRoom(
|
||||||
ev.dataTransfer.files, this.state.room.roomId, this.context,
|
ev.dataTransfer.files, this.state.room.roomId, this.context,
|
||||||
);
|
);
|
||||||
this.setState({ draggingFile: false });
|
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusComposer);
|
||||||
};
|
|
||||||
|
|
||||||
private onDragLeaveOrEnd = ev => {
|
this.setState({
|
||||||
ev.stopPropagation();
|
draggingFile: false,
|
||||||
ev.preventDefault();
|
dragCounter: this.state.dragCounter - 1,
|
||||||
this.setState({ draggingFile: false });
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private injectSticker(url, info, text) {
|
private injectSticker(url, info, text) {
|
||||||
|
@ -1397,7 +1426,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRejectButtonClicked = ev => {
|
private onRejectButtonClicked = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
rejecting: true,
|
rejecting: true,
|
||||||
});
|
});
|
||||||
|
@ -1457,7 +1486,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRejectThreepidInviteButtonClicked = ev => {
|
private onRejectThreepidInviteButtonClicked = () => {
|
||||||
// We can reject 3pid invites in the same way that we accept them,
|
// We can reject 3pid invites in the same way that we accept them,
|
||||||
// using /leave rather than /join. In the short term though, we
|
// using /leave rather than /join. In the short term though, we
|
||||||
// just ignore them.
|
// just ignore them.
|
||||||
|
@ -1720,7 +1749,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const myMembership = this.state.room.getMyMembership();
|
const myMembership = this.state.room.getMyMembership();
|
||||||
if (myMembership == 'invite') {
|
if (myMembership === "invite" && !this.state.room.isSpaceRoom()) { // SpaceRoomView handles invites itself
|
||||||
if (this.state.joining || this.state.rejecting) {
|
if (this.state.joining || this.state.rejecting) {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
@ -1765,6 +1794,19 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fileDropTarget = null;
|
||||||
|
if (this.state.draggingFile) {
|
||||||
|
fileDropTarget = (
|
||||||
|
<div className="mx_RoomView_fileDropTarget">
|
||||||
|
<img
|
||||||
|
src={require("../../../res/img/upload-big.svg")}
|
||||||
|
className="mx_RoomView_fileDropTarget_image"
|
||||||
|
/>
|
||||||
|
{ _t("Drop file here to upload") }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// We have successfully loaded this room, and are not previewing.
|
// We have successfully loaded this room, and are not previewing.
|
||||||
// Display the "normal" room view.
|
// Display the "normal" room view.
|
||||||
|
|
||||||
|
@ -1849,7 +1891,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (!this.state.canPeek) {
|
if (!this.state.canPeek && !this.state.room?.isSpaceRoom()) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
{ previewBar }
|
{ previewBar }
|
||||||
|
@ -1871,12 +1913,23 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SettingsStore.getValue("feature_spaces") && this.state.room?.isSpaceRoom()) {
|
||||||
|
return <SpaceRoomView
|
||||||
|
space={this.state.room}
|
||||||
|
justCreatedOpts={this.props.justCreatedOpts}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
onJoinButtonClicked={this.onJoinButtonClicked}
|
||||||
|
onRejectButtonClicked={this.props.threepidInvite
|
||||||
|
? this.onRejectThreepidInviteButtonClicked
|
||||||
|
: this.onRejectButtonClicked}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
const auxPanel = (
|
const auxPanel = (
|
||||||
<AuxPanel
|
<AuxPanel
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
fullHeight={false}
|
fullHeight={false}
|
||||||
userId={this.context.credentials.userId}
|
userId={this.context.credentials.userId}
|
||||||
draggingFile={this.state.draggingFile}
|
|
||||||
maxHeight={this.state.auxPanelMaxHeight}
|
maxHeight={this.state.auxPanelMaxHeight}
|
||||||
showApps={this.state.showApps}
|
showApps={this.state.showApps}
|
||||||
onResize={this.onResize}
|
onResize={this.onResize}
|
||||||
|
@ -2045,6 +2098,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
<div className="mx_RoomView_body">
|
<div className="mx_RoomView_body">
|
||||||
{auxPanel}
|
{auxPanel}
|
||||||
<div className={timelineClasses}>
|
<div className={timelineClasses}>
|
||||||
|
{fileDropTarget}
|
||||||
{topUnreadMessagesBar}
|
{topUnreadMessagesBar}
|
||||||
{jumpToBottom}
|
{jumpToBottom}
|
||||||
{messagePanel}
|
{messagePanel}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import PropTypes from 'prop-types';
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
const DEBUG_SCROLL = false;
|
const DEBUG_SCROLL = false;
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ if (DEBUG_SCROLL) {
|
||||||
* offset as normal.
|
* offset as normal.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@replaceableComponent("structures.ScrollPanel")
|
||||||
export default class ScrollPanel extends React.Component {
|
export default class ScrollPanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
/* stickyBottom: if set to true, then once the user hits the bottom of
|
/* stickyBottom: if set to true, then once the user hits the bottom of
|
||||||
|
|
|
@ -22,7 +22,9 @@ import dis from '../../dispatcher/dispatcher';
|
||||||
import {throttle} from 'lodash';
|
import {throttle} from 'lodash';
|
||||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@replaceableComponent("structures.SearchBox")
|
||||||
export default class SearchBox extends React.Component {
|
export default class SearchBox extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSearch: PropTypes.func,
|
onSearch: PropTypes.func,
|
||||||
|
|
568
src/components/structures/SpaceRoomDirectory.tsx
Normal file
|
@ -0,0 +1,568 @@
|
||||||
|
/*
|
||||||
|
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, {useMemo, useRef, useState} from "react";
|
||||||
|
import Room from "matrix-js-sdk/src/models/room";
|
||||||
|
import MatrixEvent from "matrix-js-sdk/src/models/event";
|
||||||
|
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
import {_t} from "../../languageHandler";
|
||||||
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
|
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||||
|
import FormButton from "../views/elements/FormButton";
|
||||||
|
import SearchBox from "./SearchBox";
|
||||||
|
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||||
|
import RoomName from "../views/elements/RoomName";
|
||||||
|
import {useAsyncMemo} from "../../hooks/useAsyncMemo";
|
||||||
|
import {shouldShowSpaceSettings} from "../../utils/space";
|
||||||
|
import {EnhancedMap} from "../../utils/maps";
|
||||||
|
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
||||||
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
initialText?: string;
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
export interface ISpaceSummaryRoom {
|
||||||
|
canonical_alias?: string;
|
||||||
|
aliases: string[];
|
||||||
|
avatar_url?: string;
|
||||||
|
guest_can_join: boolean;
|
||||||
|
name?: string;
|
||||||
|
num_joined_members: number
|
||||||
|
room_id: string;
|
||||||
|
topic?: string;
|
||||||
|
world_readable: boolean;
|
||||||
|
num_refs: number;
|
||||||
|
room_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISpaceSummaryEvent {
|
||||||
|
room_id: string;
|
||||||
|
event_id: string;
|
||||||
|
origin_server_ts: number;
|
||||||
|
type: string;
|
||||||
|
state_key: string;
|
||||||
|
content: {
|
||||||
|
order?: string;
|
||||||
|
suggested?: boolean;
|
||||||
|
auto_join?: boolean;
|
||||||
|
via?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
interface ISubspaceProps {
|
||||||
|
space: ISpaceSummaryRoom;
|
||||||
|
event?: MatrixEvent;
|
||||||
|
editing?: boolean;
|
||||||
|
onPreviewClick?(): void;
|
||||||
|
queueAction?(action: IAction): void;
|
||||||
|
onJoinClick?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubSpace: React.FC<ISubspaceProps> = ({
|
||||||
|
space,
|
||||||
|
editing,
|
||||||
|
event,
|
||||||
|
queueAction,
|
||||||
|
onJoinClick,
|
||||||
|
onPreviewClick,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space");
|
||||||
|
|
||||||
|
const evContent = event?.getContent();
|
||||||
|
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
||||||
|
const [removed, _setRemoved] = useState(!evContent?.via);
|
||||||
|
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const cliRoom = cli.getRoom(space.room_id);
|
||||||
|
const myMembership = cliRoom?.getMyMembership();
|
||||||
|
|
||||||
|
// TODO DRY code
|
||||||
|
let actions;
|
||||||
|
if (editing && queueAction) {
|
||||||
|
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
||||||
|
const setSuggested = () => {
|
||||||
|
_setSuggested(v => {
|
||||||
|
queueAction({
|
||||||
|
event,
|
||||||
|
removed,
|
||||||
|
suggested: !v,
|
||||||
|
});
|
||||||
|
return !v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setRemoved = () => {
|
||||||
|
_setRemoved(v => {
|
||||||
|
queueAction({
|
||||||
|
event,
|
||||||
|
removed: !v,
|
||||||
|
suggested,
|
||||||
|
});
|
||||||
|
return !v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (removed) {
|
||||||
|
actions = <React.Fragment>
|
||||||
|
<FormButton kind="danger" onClick={setRemoved} label={_t("Undo")} />
|
||||||
|
</React.Fragment>;
|
||||||
|
} else {
|
||||||
|
actions = <React.Fragment>
|
||||||
|
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
||||||
|
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
||||||
|
{ _t("No permissions")}
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
// TODO confirm remove from space click behaviour here
|
||||||
|
} else {
|
||||||
|
if (myMembership === "join") {
|
||||||
|
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
||||||
|
{ _t("You're in this space")}
|
||||||
|
</span>;
|
||||||
|
} else if (onJoinClick) {
|
||||||
|
actions = <React.Fragment>
|
||||||
|
<AccessibleButton onClick={onPreviewClick} kind="link">
|
||||||
|
{ _t("Preview") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<FormButton onClick={onJoinClick} label={_t("Join")} />
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: string;
|
||||||
|
if (space.avatar_url) {
|
||||||
|
url = mediaFromMxc(space.avatar_url).getSquareThumbnailHttp(Math.floor(24 * window.devicePixelRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_SpaceRoomDirectory_subspace">
|
||||||
|
<div className="mx_SpaceRoomDirectory_subspace_info">
|
||||||
|
<BaseAvatar name={name} idName={space.room_id} url={url} width={24} height={24} />
|
||||||
|
{ name }
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomDirectory_actions">
|
||||||
|
{ actions }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx_SpaceRoomDirectory_subspace_children">
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IAction {
|
||||||
|
event: MatrixEvent;
|
||||||
|
suggested: boolean;
|
||||||
|
removed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRoomTileProps {
|
||||||
|
room: ISpaceSummaryRoom;
|
||||||
|
event?: MatrixEvent;
|
||||||
|
editing?: boolean;
|
||||||
|
onPreviewClick(): void;
|
||||||
|
queueAction?(action: IAction): void;
|
||||||
|
onJoinClick?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinClick }: IRoomTileProps) => {
|
||||||
|
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room");
|
||||||
|
|
||||||
|
const evContent = event?.getContent();
|
||||||
|
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
||||||
|
const [removed, _setRemoved] = useState(!evContent?.via);
|
||||||
|
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const cliRoom = cli.getRoom(room.room_id);
|
||||||
|
const myMembership = cliRoom?.getMyMembership();
|
||||||
|
|
||||||
|
let actions;
|
||||||
|
if (editing && queueAction) {
|
||||||
|
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
||||||
|
const setSuggested = () => {
|
||||||
|
_setSuggested(v => {
|
||||||
|
queueAction({
|
||||||
|
event,
|
||||||
|
removed,
|
||||||
|
suggested: !v,
|
||||||
|
});
|
||||||
|
return !v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setRemoved = () => {
|
||||||
|
_setRemoved(v => {
|
||||||
|
queueAction({
|
||||||
|
event,
|
||||||
|
removed: !v,
|
||||||
|
suggested,
|
||||||
|
});
|
||||||
|
return !v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (removed) {
|
||||||
|
actions = <React.Fragment>
|
||||||
|
<FormButton kind="danger" onClick={setRemoved} label={_t("Undo")} />
|
||||||
|
</React.Fragment>;
|
||||||
|
} else {
|
||||||
|
actions = <React.Fragment>
|
||||||
|
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
||||||
|
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
||||||
|
{ _t("No permissions")}
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
// TODO confirm remove from space click behaviour here
|
||||||
|
} else {
|
||||||
|
if (myMembership === "join") {
|
||||||
|
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
||||||
|
{ _t("You're in this room")}
|
||||||
|
</span>;
|
||||||
|
} else if (onJoinClick) {
|
||||||
|
actions = <React.Fragment>
|
||||||
|
<AccessibleButton onClick={onPreviewClick} kind="link">
|
||||||
|
{ _t("Preview") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<FormButton onClick={onJoinClick} label={_t("Join")} />
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: string;
|
||||||
|
if (room.avatar_url) {
|
||||||
|
url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(Math.floor(32 * window.devicePixelRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = <React.Fragment>
|
||||||
|
<BaseAvatar name={name} idName={room.room_id} url={url} width={32} height={32} />
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomDirectory_roomTile_info">
|
||||||
|
<div className="mx_SpaceRoomDirectory_roomTile_name">
|
||||||
|
{ name }
|
||||||
|
</div>
|
||||||
|
<div className="mx_SpaceRoomDirectory_roomTile_topic">
|
||||||
|
{ room.topic }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx_SpaceRoomDirectory_roomTile_memberCount">
|
||||||
|
{ room.num_joined_members }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomDirectory_actions">
|
||||||
|
{ actions }
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
return <div className="mx_SpaceRoomDirectory_roomTile">
|
||||||
|
{ content }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AccessibleButton className="mx_SpaceRoomDirectory_roomTile" onClick={onPreviewClick}>
|
||||||
|
{ content }
|
||||||
|
</AccessibleButton>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => {
|
||||||
|
// Don't let the user view a room they won't be able to either peek or join:
|
||||||
|
// fail earlier so they don't have to click back to the directory.
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
if (!room.world_readable && !room.guest_can_join) {
|
||||||
|
dis.dispatch({ action: "require_registration" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomAlias = getDisplayAliasForRoom(room) || undefined;
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
auto_join: autoJoin,
|
||||||
|
should_peek: true,
|
||||||
|
_type: "room_directory", // instrumentation
|
||||||
|
room_alias: roomAlias,
|
||||||
|
room_id: room.room_id,
|
||||||
|
via_servers: viaServers,
|
||||||
|
oob_data: {
|
||||||
|
avatarUrl: room.avatar_url,
|
||||||
|
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
|
||||||
|
name: room.name || roomAlias || _t("Unnamed room"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IHierarchyLevelProps {
|
||||||
|
spaceId: string;
|
||||||
|
rooms: Map<string, ISpaceSummaryRoom>;
|
||||||
|
editing?: boolean;
|
||||||
|
relations: EnhancedMap<string, string[]>;
|
||||||
|
parents: Set<string>;
|
||||||
|
queueAction?(action: IAction): void;
|
||||||
|
onPreviewClick(roomId: string): void;
|
||||||
|
onRemoveFromSpaceClick?(roomId: string): void;
|
||||||
|
onJoinClick?(roomId: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HierarchyLevel = ({
|
||||||
|
spaceId,
|
||||||
|
rooms,
|
||||||
|
editing,
|
||||||
|
relations,
|
||||||
|
parents,
|
||||||
|
onPreviewClick,
|
||||||
|
onJoinClick,
|
||||||
|
queueAction,
|
||||||
|
}: IHierarchyLevelProps) => {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const space = cli.getRoom(spaceId);
|
||||||
|
// TODO respect order
|
||||||
|
const [subspaces, childRooms] = relations.get(spaceId)?.reduce((result, roomId: string) => {
|
||||||
|
if (!rooms.has(roomId)) return result; // TODO wat
|
||||||
|
result[rooms.get(roomId).room_type === RoomType.Space ? 0 : 1].push(roomId);
|
||||||
|
return result;
|
||||||
|
}, [[], []]) || [[], []];
|
||||||
|
|
||||||
|
// Don't render this subspace if it has no rooms we can show
|
||||||
|
// TODO this is broken - as a space may have subspaces we still need to show
|
||||||
|
// if (!childRooms.length) return null;
|
||||||
|
|
||||||
|
const userId = cli.getUserId();
|
||||||
|
|
||||||
|
const newParents = new Set(parents).add(spaceId);
|
||||||
|
return <React.Fragment>
|
||||||
|
{
|
||||||
|
childRooms.map(roomId => (
|
||||||
|
<RoomTile
|
||||||
|
key={roomId}
|
||||||
|
room={rooms.get(roomId)}
|
||||||
|
event={space?.currentState.maySendStateEvent(EventType.SpaceChild, userId)
|
||||||
|
? space?.currentState.getStateEvents(EventType.SpaceChild, roomId)
|
||||||
|
: undefined}
|
||||||
|
editing={editing}
|
||||||
|
queueAction={queueAction}
|
||||||
|
onPreviewClick={() => {
|
||||||
|
onPreviewClick(roomId);
|
||||||
|
}}
|
||||||
|
onJoinClick={onJoinClick ? () => {
|
||||||
|
onJoinClick(roomId);
|
||||||
|
} : undefined}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
subspaces.filter(roomId => !newParents.has(roomId)).map(roomId => (
|
||||||
|
<SubSpace
|
||||||
|
key={roomId}
|
||||||
|
space={rooms.get(roomId)}
|
||||||
|
event={space?.currentState.getStateEvents(EventType.SpaceChild, roomId)}
|
||||||
|
editing={editing}
|
||||||
|
queueAction={queueAction}
|
||||||
|
onPreviewClick={() => {
|
||||||
|
onPreviewClick(roomId);
|
||||||
|
}}
|
||||||
|
onJoinClick={() => {
|
||||||
|
onJoinClick(roomId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HierarchyLevel
|
||||||
|
spaceId={roomId}
|
||||||
|
rooms={rooms}
|
||||||
|
editing={editing}
|
||||||
|
relations={relations}
|
||||||
|
parents={newParents}
|
||||||
|
onPreviewClick={onPreviewClick}
|
||||||
|
onJoinClick={onJoinClick}
|
||||||
|
queueAction={queueAction}
|
||||||
|
/>
|
||||||
|
</SubSpace>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => {
|
||||||
|
// TODO pagination
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const [query, setQuery] = useState(initialText);
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
|
const onCreateRoomClick = () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_create_room',
|
||||||
|
public: true,
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
// stored within a ref as we don't need to re-render when it changes
|
||||||
|
const pendingActions = useRef(new Map<string, IAction>());
|
||||||
|
|
||||||
|
let adminButton;
|
||||||
|
if (shouldShowSpaceSettings(cli, space)) { // TODO this is an imperfect test
|
||||||
|
const onManageButtonClicked = () => {
|
||||||
|
setIsEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveButtonClicked = () => {
|
||||||
|
// TODO setBusy
|
||||||
|
pendingActions.current.forEach(({event, suggested, removed}) => {
|
||||||
|
const content = {
|
||||||
|
...event.getContent(),
|
||||||
|
suggested,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (removed) {
|
||||||
|
delete content["via"];
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.sendStateEvent(event.getRoomId(), event.getType(), content, event.getStateKey());
|
||||||
|
});
|
||||||
|
setIsEditing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
adminButton = <React.Fragment>
|
||||||
|
<FormButton label={_t("Save changes")} onClick={onSaveButtonClicked} />
|
||||||
|
<span>{ _t("Promoted to users") }</span>
|
||||||
|
</React.Fragment>;
|
||||||
|
} else {
|
||||||
|
adminButton = <FormButton label={_t("Manage rooms")} onClick={onManageButtonClicked} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rooms, relations, viaMap] = useAsyncMemo(async () => {
|
||||||
|
try {
|
||||||
|
const data = await cli.getSpaceSummary(space.roomId);
|
||||||
|
|
||||||
|
const parentChildRelations = new EnhancedMap<string, string[]>();
|
||||||
|
const viaMap = new EnhancedMap<string, Set<string>>();
|
||||||
|
data.events.map((ev: ISpaceSummaryEvent) => {
|
||||||
|
if (ev.type === EventType.SpaceChild) {
|
||||||
|
parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key);
|
||||||
|
}
|
||||||
|
if (Array.isArray(ev.content["via"])) {
|
||||||
|
const set = viaMap.getOrCreate(ev.state_key, new Set());
|
||||||
|
ev.content["via"].forEach(via => set.add(via));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [data.rooms, parentChildRelations, viaMap];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}, [space], []);
|
||||||
|
|
||||||
|
const roomsMap = useMemo(() => {
|
||||||
|
if (!rooms) return null;
|
||||||
|
const lcQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
const filteredRooms = rooms.filter(r => {
|
||||||
|
return r.room_type === RoomType.Space // always include spaces to allow filtering of sub-space rooms
|
||||||
|
|| r.name?.toLowerCase().includes(lcQuery)
|
||||||
|
|| r.topic?.toLowerCase().includes(lcQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Map<string, ISpaceSummaryRoom>(filteredRooms.map(r => [r.room_id, r]));
|
||||||
|
// const root = rooms.get(space.roomId);
|
||||||
|
}, [rooms, query]);
|
||||||
|
|
||||||
|
const title = <React.Fragment>
|
||||||
|
<RoomAvatar room={space} height={40} width={40} />
|
||||||
|
<div>
|
||||||
|
<h1>{ _t("Explore rooms") }</h1>
|
||||||
|
<div><RoomName room={space} /></div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
|
const explanation =
|
||||||
|
_t("If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", null,
|
||||||
|
{a: sub => {
|
||||||
|
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (roomsMap) {
|
||||||
|
content = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
||||||
|
<HierarchyLevel
|
||||||
|
spaceId={space.roomId}
|
||||||
|
rooms={roomsMap}
|
||||||
|
editing={isEditing}
|
||||||
|
relations={relations}
|
||||||
|
parents={new Set()}
|
||||||
|
queueAction={action => {
|
||||||
|
pendingActions.current.set(action.event.room_id, action);
|
||||||
|
}}
|
||||||
|
onPreviewClick={roomId => {
|
||||||
|
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), false);
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
onJoinClick={(roomId) => {
|
||||||
|
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), true);
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AutoHideScrollbar>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO loading state/error state
|
||||||
|
return (
|
||||||
|
<BaseDialog className="mx_SpaceRoomDirectory" hasCancel={true} onFinished={onFinished} title={title}>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
{ explanation }
|
||||||
|
|
||||||
|
<SearchBox
|
||||||
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
|
placeholder={ _t("Find a room...") }
|
||||||
|
onSearch={setQuery}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomDirectory_listHeader">
|
||||||
|
{ adminButton }
|
||||||
|
</div>
|
||||||
|
{ content }
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpaceRoomDirectory;
|
||||||
|
|
||||||
|
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
|
||||||
|
// but works with the objects we get from the public room list
|
||||||
|
function getDisplayAliasForRoom(room: ISpaceSummaryRoom) {
|
||||||
|
return room.canonical_alias || (room.aliases ? room.aliases[0] : "");
|
||||||
|
}
|
656
src/components/structures/SpaceRoomView.tsx
Normal file
|
@ -0,0 +1,656 @@
|
||||||
|
/*
|
||||||
|
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, {RefObject, useContext, useRef, useState} from "react";
|
||||||
|
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||||
|
import {_t} from "../../languageHandler";
|
||||||
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
|
import RoomName from "../views/elements/RoomName";
|
||||||
|
import RoomTopic from "../views/elements/RoomTopic";
|
||||||
|
import InlineSpinner from "../views/elements/InlineSpinner";
|
||||||
|
import FormButton from "../views/elements/FormButton";
|
||||||
|
import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
|
||||||
|
import {useRoomMembers} from "../../hooks/useRoomMembers";
|
||||||
|
import createRoom, {IOpts, Preset} from "../../createRoom";
|
||||||
|
import Field from "../views/elements/Field";
|
||||||
|
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
||||||
|
import StyledRadioGroup from "../views/elements/StyledRadioGroup";
|
||||||
|
import withValidation from "../views/elements/Validation";
|
||||||
|
import * as Email from "../../email";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
import {Action} from "../../dispatcher/actions";
|
||||||
|
import ResizeNotifier from "../../utils/ResizeNotifier"
|
||||||
|
import MainSplit from './MainSplit';
|
||||||
|
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||||
|
import {ActionPayload} from "../../dispatcher/payloads";
|
||||||
|
import RightPanel from "./RightPanel";
|
||||||
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
|
import {EventSubscription} from "fbemitter";
|
||||||
|
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||||
|
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||||
|
import {useStateArray} from "../../hooks/useStateArray";
|
||||||
|
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||||
|
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
||||||
|
import {HierarchyLevel, ISpaceSummaryEvent, ISpaceSummaryRoom, showRoom} from "./SpaceRoomDirectory";
|
||||||
|
import {useAsyncMemo} from "../../hooks/useAsyncMemo";
|
||||||
|
import {EnhancedMap} from "../../utils/maps";
|
||||||
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||||
|
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
justCreatedOpts?: IOpts;
|
||||||
|
resizeNotifier: ResizeNotifier;
|
||||||
|
onJoinButtonClicked(): void;
|
||||||
|
onRejectButtonClicked(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
phase: Phase;
|
||||||
|
showRightPanel: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Phase {
|
||||||
|
Landing,
|
||||||
|
PublicCreateRooms,
|
||||||
|
PublicShare,
|
||||||
|
PrivateScope,
|
||||||
|
PrivateInvite,
|
||||||
|
PrivateCreateRooms,
|
||||||
|
PrivateExistingRooms,
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoomMemberCount = ({ room, children }) => {
|
||||||
|
const members = useRoomMembers(room);
|
||||||
|
const count = members.length;
|
||||||
|
|
||||||
|
if (children) return children(count);
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useMyRoomMembership = (room: Room) => {
|
||||||
|
const [membership, setMembership] = useState(room.getMyMembership());
|
||||||
|
useEventEmitter(room, "Room.myMembership", () => {
|
||||||
|
setMembership(room.getMyMembership());
|
||||||
|
});
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
|
||||||
|
let inviterSection;
|
||||||
|
let joinButtons;
|
||||||
|
if (myMembership === "invite") {
|
||||||
|
const inviteSender = space.getMember(cli.getUserId())?.events.member?.getSender();
|
||||||
|
const inviter = inviteSender && space.getMember(inviteSender);
|
||||||
|
|
||||||
|
if (inviteSender) {
|
||||||
|
inviterSection = <div className="mx_SpaceRoomView_preview_inviter">
|
||||||
|
<MemberAvatar member={inviter} width={32} height={32} />
|
||||||
|
<div>
|
||||||
|
<div className="mx_SpaceRoomView_preview_inviter_name">
|
||||||
|
{ _t("<inviter/> invites you", {}, {
|
||||||
|
inviter: () => <b>{ inviter.name || inviteSender }</b>,
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
{ inviter ? <div className="mx_SpaceRoomView_preview_inviter_mxid">
|
||||||
|
{ inviteSender }
|
||||||
|
</div> : null }
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
joinButtons = <>
|
||||||
|
<FormButton label={_t("Reject")} kind="secondary" onClick={onRejectButtonClicked} />
|
||||||
|
<FormButton label={_t("Accept")} onClick={onJoinButtonClicked} />
|
||||||
|
</>;
|
||||||
|
} else {
|
||||||
|
joinButtons = <FormButton label={_t("Join")} onClick={onJoinButtonClicked} />
|
||||||
|
}
|
||||||
|
|
||||||
|
let visibilitySection;
|
||||||
|
if (space.getJoinRule() === "public") {
|
||||||
|
visibilitySection = <span className="mx_SpaceRoomView_preview_info_public">
|
||||||
|
{ _t("Public space") }
|
||||||
|
</span>;
|
||||||
|
} else {
|
||||||
|
visibilitySection = <span className="mx_SpaceRoomView_preview_info_private">
|
||||||
|
{ _t("Private space") }
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_SpaceRoomView_preview">
|
||||||
|
{ inviterSection }
|
||||||
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
|
<h1 className="mx_SpaceRoomView_preview_name">
|
||||||
|
<RoomName room={space} />
|
||||||
|
</h1>
|
||||||
|
<div className="mx_SpaceRoomView_preview_info">
|
||||||
|
{ visibilitySection }
|
||||||
|
<RoomMemberCount room={space}>
|
||||||
|
{(count) => count > 0 ? (
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_SpaceRoomView_preview_memberCount"
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.RoomMemberList,
|
||||||
|
refireParams: { space },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("%(count)s members", { count }) }
|
||||||
|
</AccessibleButton>
|
||||||
|
) : null}
|
||||||
|
</RoomMemberCount>
|
||||||
|
</div>
|
||||||
|
<RoomTopic room={space}>
|
||||||
|
{(topic, ref) =>
|
||||||
|
<div className="mx_SpaceRoomView_preview_topic" ref={ref}>
|
||||||
|
{ topic }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</RoomTopic>
|
||||||
|
<div className="mx_SpaceRoomView_preview_joinButtons">
|
||||||
|
{ joinButtons }
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceLanding = ({ space }) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
const userId = cli.getUserId();
|
||||||
|
|
||||||
|
let inviteButton;
|
||||||
|
if (myMembership === "join" && space.canInvite(userId)) {
|
||||||
|
inviteButton = (
|
||||||
|
<AccessibleButton className="mx_SpaceRoomView_landing_inviteButton" onClick={() => {
|
||||||
|
showRoomInviteDialog(space.roomId);
|
||||||
|
}}>
|
||||||
|
{ _t("Invite people") }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||||
|
|
||||||
|
const [_, forceUpdate] = useStateToggle(false); // TODO
|
||||||
|
|
||||||
|
let addRoomButtons;
|
||||||
|
if (canAddRooms) {
|
||||||
|
addRoomButtons = <React.Fragment>
|
||||||
|
<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;
|
||||||
|
if (shouldShowSpaceSettings(cli, space)) {
|
||||||
|
settingsButton = <AccessibleButton className="mx_SpaceRoomView_landing_settingsButton" onClick={() => {
|
||||||
|
showSpaceSettings(cli, space);
|
||||||
|
}}>
|
||||||
|
{ _t("Settings") }
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [loading, roomsMap, relations, numRooms] = useAsyncMemo(async () => {
|
||||||
|
try {
|
||||||
|
const data = await cli.getSpaceSummary(space.roomId, undefined, myMembership !== "join");
|
||||||
|
|
||||||
|
const parentChildRelations = new EnhancedMap<string, string[]>();
|
||||||
|
data.events.map((ev: ISpaceSummaryEvent) => {
|
||||||
|
if (ev.type === EventType.SpaceChild) {
|
||||||
|
parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const roomsMap = new Map<string, ISpaceSummaryRoom>(data.rooms.map(r => [r.room_id, r]));
|
||||||
|
const numRooms = data.rooms.filter(r => r.room_type !== RoomType.Space).length;
|
||||||
|
return [false, roomsMap, parentChildRelations, numRooms];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return [false];
|
||||||
|
}, [space, _], [true]);
|
||||||
|
|
||||||
|
let previewRooms;
|
||||||
|
if (roomsMap) {
|
||||||
|
previewRooms = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
||||||
|
<div className="mx_SpaceRoomDirectory_roomCount">
|
||||||
|
<h3>{ myMembership === "join" ? _t("Rooms") : _t("Default Rooms")}</h3>
|
||||||
|
<span>{ numRooms }</span>
|
||||||
|
</div>
|
||||||
|
<HierarchyLevel
|
||||||
|
spaceId={space.roomId}
|
||||||
|
rooms={roomsMap}
|
||||||
|
editing={false}
|
||||||
|
relations={relations}
|
||||||
|
parents={new Set()}
|
||||||
|
onPreviewClick={roomId => {
|
||||||
|
showRoom(roomsMap.get(roomId), [], false); // TODO
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AutoHideScrollbar>;
|
||||||
|
} else if (loading) {
|
||||||
|
previewRooms = <InlineSpinner />;
|
||||||
|
} else {
|
||||||
|
previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_SpaceRoomView_landing">
|
||||||
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
|
<div className="mx_SpaceRoomView_landing_name">
|
||||||
|
<RoomName room={space}>
|
||||||
|
{(name) => {
|
||||||
|
const tags = { name: () => <div className="mx_SpaceRoomView_landing_nameRow">
|
||||||
|
<h1>{ name }</h1>
|
||||||
|
<RoomMemberCount room={space}>
|
||||||
|
{(count) => count > 0 ? (
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_SpaceRoomView_landing_memberCount"
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.RoomMemberList,
|
||||||
|
refireParams: { space },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("%(count)s members", { count }) }
|
||||||
|
</AccessibleButton>
|
||||||
|
) : null}
|
||||||
|
</RoomMemberCount>
|
||||||
|
</div> };
|
||||||
|
if (shouldShowSpaceSettings(cli, space)) {
|
||||||
|
if (space.getJoinRule() === "public") {
|
||||||
|
return _t("Your public space <name/>", {}, tags) as JSX.Element;
|
||||||
|
} else {
|
||||||
|
return _t("Your private space <name/>", {}, tags) as JSX.Element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _t("Welcome to <name/>", {}, tags) as JSX.Element;
|
||||||
|
}}
|
||||||
|
</RoomName>
|
||||||
|
</div>
|
||||||
|
<div className="mx_SpaceRoomView_landing_topic">
|
||||||
|
<RoomTopic room={space} />
|
||||||
|
</div>
|
||||||
|
<div className="mx_SpaceRoomView_landing_adminButtons">
|
||||||
|
{ inviteButton }
|
||||||
|
{ addRoomButtons }
|
||||||
|
{ settingsButton }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ previewRooms }
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const numFields = 3;
|
||||||
|
const placeholders = [_t("General"), _t("Random"), _t("Support")];
|
||||||
|
// TODO vary default prefills for "Just Me" spaces
|
||||||
|
const [roomNames, setRoomName] = useStateArray(numFields, [_t("General"), _t("Random"), ""]);
|
||||||
|
const fields = new Array(numFields).fill(0).map((_, i) => {
|
||||||
|
const name = "roomName" + i;
|
||||||
|
return <Field
|
||||||
|
key={name}
|
||||||
|
name={name}
|
||||||
|
type="text"
|
||||||
|
label={_t("Room name")}
|
||||||
|
placeholder={placeholders[i]}
|
||||||
|
value={roomNames[i]}
|
||||||
|
onChange={ev => setRoomName(i, ev.target.value)}
|
||||||
|
/>;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onNextClick = async () => {
|
||||||
|
setError("");
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
await Promise.all(roomNames.map(name => name.trim()).filter(Boolean).map(name => {
|
||||||
|
return createRoom({
|
||||||
|
createOpts: {
|
||||||
|
preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
spinner: false,
|
||||||
|
encryption: false,
|
||||||
|
andView: false,
|
||||||
|
inlineErrors: true,
|
||||||
|
parentSpace: space,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
onFinished();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to create initial space rooms", e);
|
||||||
|
setError(_t("Failed to create initial space rooms"));
|
||||||
|
}
|
||||||
|
setBusy(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let onClick = onFinished;
|
||||||
|
let buttonLabel = _t("Skip for now");
|
||||||
|
if (roomNames.some(name => name.trim())) {
|
||||||
|
onClick = onNextClick;
|
||||||
|
buttonLabel = busy ? _t("Creating rooms...") : _t("Next")
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<h1>{ title }</h1>
|
||||||
|
<div className="mx_SpaceRoomView_description">{ description }</div>
|
||||||
|
|
||||||
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
|
{ fields }
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
|
<FormButton
|
||||||
|
label={buttonLabel}
|
||||||
|
disabled={busy}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceSetupPublicShare = ({ space, onFinished }) => {
|
||||||
|
return <div className="mx_SpaceRoomView_publicShare">
|
||||||
|
<h1>{ _t("Share your public space") }</h1>
|
||||||
|
<div className="mx_SpacePublicShare_description">{ _t("At the moment only you can see it.") }</div>
|
||||||
|
|
||||||
|
<SpacePublicShare space={space} onFinished={onFinished} />
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
|
<FormButton label={_t("Finish")} onClick={onFinished} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceSetupPrivateScope = ({ onFinished }) => {
|
||||||
|
const [option, setOption] = useState<string>(null);
|
||||||
|
|
||||||
|
return <div className="mx_SpaceRoomView_privateScope">
|
||||||
|
<h1>{ _t("Who are you working with?") }</h1>
|
||||||
|
<div className="mx_SpaceRoomView_description">{ _t("Ensure the right people have access to the space.") }</div>
|
||||||
|
|
||||||
|
<StyledRadioGroup
|
||||||
|
name="privateSpaceScope"
|
||||||
|
value={option}
|
||||||
|
onChange={setOption}
|
||||||
|
definitions={[
|
||||||
|
{
|
||||||
|
value: "justMe",
|
||||||
|
className: "mx_SpaceRoomView_privateScope_justMeButton",
|
||||||
|
label: <React.Fragment>
|
||||||
|
<h3>{ _t("Just Me") }</h3>
|
||||||
|
<div>{ _t("A private space just for you") }</div>
|
||||||
|
</React.Fragment>,
|
||||||
|
}, {
|
||||||
|
value: "meAndMyTeammates",
|
||||||
|
className: "mx_SpaceRoomView_privateScope_meAndMyTeammatesButton",
|
||||||
|
label: <React.Fragment>
|
||||||
|
<h3>{ _t("Me and my teammates") }</h3>
|
||||||
|
<div>{ _t("A private space for you and your teammates") }</div>
|
||||||
|
</React.Fragment>,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
|
<FormButton label={_t("Next")} disabled={!option} onClick={() => onFinished(option !== "justMe")} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateEmailRules = withValidation({
|
||||||
|
rules: [{
|
||||||
|
key: "email",
|
||||||
|
test: ({ value }) => !value || Email.looksValid(value),
|
||||||
|
invalid: () => _t("Doesn't look like a valid email address"),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const numFields = 3;
|
||||||
|
const fieldRefs: RefObject<Field>[] = [useRef(), useRef(), useRef()];
|
||||||
|
const [emailAddresses, setEmailAddress] = useStateArray(numFields, "");
|
||||||
|
const fields = new Array(numFields).fill(0).map((_, i) => {
|
||||||
|
const name = "emailAddress" + i;
|
||||||
|
return <Field
|
||||||
|
key={name}
|
||||||
|
name={name}
|
||||||
|
type="text"
|
||||||
|
label={_t("Email address")}
|
||||||
|
placeholder={_t("Email")}
|
||||||
|
value={emailAddresses[i]}
|
||||||
|
onChange={ev => setEmailAddress(i, ev.target.value)}
|
||||||
|
ref={fieldRefs[i]}
|
||||||
|
onValidate={validateEmailRules}
|
||||||
|
/>;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onNextClick = async () => {
|
||||||
|
setError("");
|
||||||
|
for (let i = 0; i < fieldRefs.length; i++) {
|
||||||
|
const fieldRef = fieldRefs[i];
|
||||||
|
const valid = await fieldRef.current.validate({ allowEmpty: true });
|
||||||
|
|
||||||
|
if (valid === false) { // true/null are allowed
|
||||||
|
fieldRef.current.focus();
|
||||||
|
fieldRef.current.validate({ allowEmpty: true, focused: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setBusy(true);
|
||||||
|
const targetIds = emailAddresses.map(name => name.trim()).filter(Boolean);
|
||||||
|
try {
|
||||||
|
const result = await inviteMultipleToRoom(space.roomId, targetIds);
|
||||||
|
|
||||||
|
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === "error");
|
||||||
|
if (failedUsers.length > 0) {
|
||||||
|
console.log("Failed to invite users to space: ", result);
|
||||||
|
setError(_t("Failed to invite the following users to your space: %(csvUsers)s", {
|
||||||
|
csvUsers: failedUsers.join(", "),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
onFinished();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to invite users to space: ", err);
|
||||||
|
setError(_t("We couldn't invite those users. Please check the users you want to invite and try again."));
|
||||||
|
}
|
||||||
|
setBusy(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className="mx_SpaceRoomView_inviteTeammates">
|
||||||
|
<h1>{ _t("Invite your teammates") }</h1>
|
||||||
|
<div className="mx_SpaceRoomView_description">{ _t("Ensure the right people have access to the space.") }</div>
|
||||||
|
|
||||||
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
|
{ fields }
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomView_inviteTeammates_buttons">
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_SpaceRoomView_inviteTeammates_inviteDialogButton"
|
||||||
|
onClick={() => showRoomInviteDialog(space.roomId)}
|
||||||
|
>
|
||||||
|
{ _t("Invite by username") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
|
<AccessibleButton onClick={onFinished} kind="link">{_t("Skip for now")}</AccessibleButton>
|
||||||
|
<FormButton label={busy ? _t("Inviting...") : _t("Next")} disabled={busy} onClick={onNextClick} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
|
private readonly creator: string;
|
||||||
|
private readonly dispatcherRef: string;
|
||||||
|
private readonly rightPanelStoreToken: EventSubscription;
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
let phase = Phase.Landing;
|
||||||
|
|
||||||
|
this.creator = this.props.space.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
||||||
|
const showSetup = this.props.justCreatedOpts && this.context.getUserId() === this.creator;
|
||||||
|
|
||||||
|
if (showSetup) {
|
||||||
|
phase = this.props.justCreatedOpts.createOpts.preset === Preset.PublicChat
|
||||||
|
? Phase.PublicCreateRooms : Phase.PrivateScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
phase,
|
||||||
|
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
|
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
|
this.rightPanelStoreToken.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRightPanelStoreUpdate = () => {
|
||||||
|
this.setState({
|
||||||
|
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onAction = (payload: ActionPayload) => {
|
||||||
|
if (payload.action !== Action.ViewUser && payload.action !== "view_3pid_invite") return;
|
||||||
|
|
||||||
|
if (payload.action === Action.ViewUser && payload.member) {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.SpaceMemberInfo,
|
||||||
|
refireParams: {
|
||||||
|
space: this.props.space,
|
||||||
|
member: payload.member,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (payload.action === "view_3pid_invite" && payload.event) {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.Space3pidMemberInfo,
|
||||||
|
refireParams: {
|
||||||
|
space: this.props.space,
|
||||||
|
event: payload.event,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.SpaceMemberList,
|
||||||
|
refireParams: { space: this.props.space },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderBody() {
|
||||||
|
switch (this.state.phase) {
|
||||||
|
case Phase.Landing:
|
||||||
|
if (this.props.space.getMyMembership() === "join") {
|
||||||
|
return <SpaceLanding space={this.props.space} />;
|
||||||
|
} else {
|
||||||
|
return <SpacePreview
|
||||||
|
space={this.props.space}
|
||||||
|
onJoinButtonClicked={this.props.onJoinButtonClicked}
|
||||||
|
onRejectButtonClicked={this.props.onRejectButtonClicked}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
case Phase.PublicCreateRooms:
|
||||||
|
return <SpaceSetupFirstRooms
|
||||||
|
space={this.props.space}
|
||||||
|
title={_t("What are some things you want to discuss?")}
|
||||||
|
description={_t("We'll create rooms for each topic.")}
|
||||||
|
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
||||||
|
/>;
|
||||||
|
case Phase.PublicShare:
|
||||||
|
return <SpaceSetupPublicShare
|
||||||
|
space={this.props.space}
|
||||||
|
onFinished={() => this.setState({ phase: Phase.Landing })}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
case Phase.PrivateScope:
|
||||||
|
return <SpaceSetupPrivateScope
|
||||||
|
onFinished={(invite: boolean) => {
|
||||||
|
this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateCreateRooms });
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
case Phase.PrivateInvite:
|
||||||
|
return <SpaceSetupPrivateInvite
|
||||||
|
space={this.props.space}
|
||||||
|
onFinished={() => this.setState({ phase: Phase.PrivateCreateRooms })}
|
||||||
|
/>;
|
||||||
|
case Phase.PrivateCreateRooms:
|
||||||
|
return <SpaceSetupFirstRooms
|
||||||
|
space={this.props.space}
|
||||||
|
title={_t("What projects are you working on?")}
|
||||||
|
description={_t("We'll create rooms for each of them. You can add existing rooms after setup.")}
|
||||||
|
onFinished={() => this.setState({ phase: Phase.Landing })}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const rightPanel = this.state.showRightPanel && this.state.phase === Phase.Landing
|
||||||
|
? <RightPanel room={this.props.space} resizeNotifier={this.props.resizeNotifier} />
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return <main className="mx_SpaceRoomView">
|
||||||
|
<ErrorBoundary>
|
||||||
|
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
||||||
|
{ this.renderBody() }
|
||||||
|
</MainSplit>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</main>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import * as React from "react";
|
||||||
import {_t} from '../../languageHandler';
|
import {_t} from '../../languageHandler';
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a tab for the TabbedView.
|
* Represents a tab for the TabbedView.
|
||||||
|
@ -45,6 +46,7 @@ interface IState {
|
||||||
activeTabIndex: number;
|
activeTabIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.TabbedView")
|
||||||
export default class TabbedView extends React.Component<IProps, IState> {
|
export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
|
@ -37,6 +37,7 @@ import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
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";
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -55,6 +56,7 @@ if (DEBUG) {
|
||||||
*
|
*
|
||||||
* Also responsible for handling and sending read receipts.
|
* Also responsible for handling and sending read receipts.
|
||||||
*/
|
*/
|
||||||
|
@replaceableComponent("structures.TimelinePanel")
|
||||||
class TimelinePanel extends React.Component {
|
class TimelinePanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// The js-sdk EventTimelineSet object for the timeline sequence we are
|
// The js-sdk EventTimelineSet object for the timeline sequence we are
|
||||||
|
|
|
@ -17,12 +17,14 @@ limitations under the License.
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ToastStore, {IToast} from "../../stores/ToastStore";
|
import ToastStore, {IToast} from "../../stores/ToastStore";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
toasts: IToast<any>[];
|
toasts: IToast<any>[];
|
||||||
countSeen: number;
|
countSeen: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.ToastContainer")
|
||||||
export default class ToastContainer extends React.Component<{}, IState> {
|
export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
Copyright 2019 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 PropTypes from 'prop-types';
|
|
||||||
import ContentMessages from '../../ContentMessages';
|
|
||||||
import dis from "../../dispatcher/dispatcher";
|
|
||||||
import filesize from "filesize";
|
|
||||||
import { _t } from '../../languageHandler';
|
|
||||||
|
|
||||||
export default class UploadBar extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
room: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
this.mounted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.mounted = false;
|
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
onAction = payload => {
|
|
||||||
switch (payload.action) {
|
|
||||||
case 'upload_progress':
|
|
||||||
case 'upload_finished':
|
|
||||||
case 'upload_canceled':
|
|
||||||
case 'upload_failed':
|
|
||||||
if (this.mounted) this.forceUpdate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const uploads = ContentMessages.sharedInstance().getCurrentUploads();
|
|
||||||
|
|
||||||
// for testing UI... - also fix up the ContentMessages.getCurrentUploads().length
|
|
||||||
// check in RoomView
|
|
||||||
//
|
|
||||||
// uploads = [{
|
|
||||||
// roomId: this.props.room.roomId,
|
|
||||||
// loaded: 123493,
|
|
||||||
// total: 347534,
|
|
||||||
// fileName: "testing_fooble.jpg",
|
|
||||||
// }];
|
|
||||||
|
|
||||||
if (uploads.length == 0) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
let upload;
|
|
||||||
for (let i = 0; i < uploads.length; ++i) {
|
|
||||||
if (uploads[i].roomId == this.props.room.roomId) {
|
|
||||||
upload = uploads[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!upload) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const innerProgressStyle = {
|
|
||||||
width: ((upload.loaded / (upload.total || 1)) * 100) + '%',
|
|
||||||
};
|
|
||||||
let uploadedSize = filesize(upload.loaded);
|
|
||||||
const totalSize = filesize(upload.total);
|
|
||||||
if (uploadedSize.replace(/^.* /, '') === totalSize.replace(/^.* /, '')) {
|
|
||||||
uploadedSize = uploadedSize.replace(/ .*/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// MUST use var name 'count' for pluralization to kick in
|
|
||||||
const uploadText = _t(
|
|
||||||
"Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)},
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx_UploadBar">
|
|
||||||
<div className="mx_UploadBar_uploadProgressOuter">
|
|
||||||
<div className="mx_UploadBar_uploadProgressInner" style={innerProgressStyle}></div>
|
|
||||||
</div>
|
|
||||||
<img className="mx_UploadBar_uploadIcon mx_filterFlipColor" src={require("../../../res/img/fileicon.png")} width="17" height="22" />
|
|
||||||
<img className="mx_UploadBar_uploadCancel mx_filterFlipColor" src={require("../../../res/img/cancel.svg")} width="18" height="18"
|
|
||||||
onClick={function() { ContentMessages.sharedInstance().cancelUpload(upload.promise); }}
|
|
||||||
/>
|
|
||||||
<div className="mx_UploadBar_uploadBytes">
|
|
||||||
{ uploadedSize } / { totalSize }
|
|
||||||
</div>
|
|
||||||
<div className="mx_UploadBar_uploadFilename">{ uploadText }</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|