Compare commits
24 Commits
v1.11.94
...
robin/call
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
569d525c6e | ||
|
|
7799cb2ec5 | ||
|
|
e47d7aaaff | ||
|
|
8857c07acb | ||
|
|
28ed506fe1 | ||
|
|
76b3be6263 | ||
|
|
6c768b8b32 | ||
|
|
809ada17a4 | ||
|
|
c7762a80f1 | ||
|
|
261923832d | ||
|
|
3daa1bf06a | ||
|
|
e5c8d7dbf0 | ||
|
|
441119ca3a | ||
|
|
acb3e781a4 | ||
|
|
3fb1f6ef4d | ||
|
|
cbfbfad959 | ||
|
|
7546bbc1f0 | ||
|
|
6a10c86d7a | ||
|
|
6f9e3bfe3e | ||
|
|
61d55462df | ||
|
|
d391c69e53 | ||
|
|
a6afff9759 | ||
|
|
073d8e0b86 | ||
|
|
ecf5d720b0 |
6
.github/workflows/docker.yaml
vendored
@@ -22,13 +22,13 @@ jobs:
|
||||
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3
|
||||
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3
|
||||
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3
|
||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Skip SonarCloud in merge queue
|
||||
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
||||
uses: guibranco/github-status-action-v2@119b3320db3f04d89e91df840844b92d57ce3468
|
||||
uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
|
||||
67
docs/MVVM.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# MVVM
|
||||
|
||||
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
|
||||
|
||||
1. Model: This is where the business logic and data resides.
|
||||
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
|
||||
3. View: This is the UI code itself and depends on the view model.
|
||||
|
||||
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
|
||||
|
||||
### Practical guidelines for MVVM in element-web
|
||||
|
||||
#### Model
|
||||
|
||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||
|
||||
#### View Model
|
||||
|
||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||
```ts
|
||||
inteface FooViewState {
|
||||
somethingUseful: string;
|
||||
somethingElse: BarType;
|
||||
update: () => Promise<void>
|
||||
...
|
||||
}
|
||||
```
|
||||
3. Any react state that your UI needs must be in the view model.
|
||||
|
||||
#### View
|
||||
|
||||
1. Views are simple react components (eg: `FooView`).
|
||||
2. Views usually start by calling the view model hook, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||
const vm = useFooViewModel();
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
3. Views are also allowed to accept the view model as a prop, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
4. Multiple views can share the same view model if necessary.
|
||||
|
||||
### Benefits
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
|
||||
### Example
|
||||
|
||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||
@@ -163,14 +163,14 @@ These two options describe the various availability for the application. When th
|
||||
such as trying to get the user to use an Android app or the desktop app for encrypted search, the config options will be looked
|
||||
at to see if the link should be to somewhere else.
|
||||
|
||||
Starting with `desktop_builds`, the following subproperties are available:
|
||||
Starting with `desktop_builds`, the following sub-properties are available:
|
||||
|
||||
1. `available`: Required. When `true`, the desktop app can be downloaded from somewhere.
|
||||
2. `logo`: Required. A URL to a logo (SVG), intended to be shown at 24x24 pixels.
|
||||
3. `url`: Required. The download URL for the app. This is used as a hyperlink.
|
||||
4. `url_macos`: Optional. Direct link to download macOS desktop app.
|
||||
5. `url_win32`: Optional. Direct link to download Windows 32-bit desktop app.
|
||||
6. `url_win64`: Optional. Direct link to download Windows 64-bit desktop app.
|
||||
5. `url_win64`: Optional. Direct link to download Windows x86 64-bit desktop app.
|
||||
6. `url_win64arm`: Optional. Direct link to download Windows ARM 64-bit desktop app.
|
||||
7. `url_linux`: Optional. Direct link to download Linux desktop app.
|
||||
|
||||
When `desktop_builds` is not specified at all, the app will assume desktop downloads are available from https://element.io
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"@types/react-dom": "18.3.5",
|
||||
"oidc-client-ts": "3.1.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001697",
|
||||
"caniuse-lite": "1.0.30001699",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
@@ -88,7 +88,7 @@
|
||||
"@matrix-org/emojibase-bindings": "^1.3.4",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^8.0.0",
|
||||
"@sentry/browser": "^9.0.0",
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@types/react-virtualized": "^9.21.30",
|
||||
"@vector-im/compound-design-tokens": "^3.0.0",
|
||||
@@ -274,7 +274,7 @@
|
||||
"postcss-preset-env": "^10.0.0",
|
||||
"postcss-scss": "^4.0.4",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "3.4.2",
|
||||
"prettier": "3.5.1",
|
||||
"process": "^0.11.10",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.49.1-noble
|
||||
FROM mcr.microsoft.com/playwright:v1.50.1-noble
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
||||
@@ -6,22 +6,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect, test as base } from "../../element-web-test";
|
||||
import { type CredentialsWithDisplayName, expect, test as base } from "../../element-web-test";
|
||||
import { selectHomeserver } from "../utils";
|
||||
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
|
||||
import { isDendrite } from "../../plugins/homeserver/dendrite";
|
||||
import { type Credentials } from "../../plugins/homeserver";
|
||||
|
||||
const email = "user@nowhere.dummy";
|
||||
|
||||
const test = base.extend<{ credentials: Pick<Credentials, "username" | "password"> }>({
|
||||
const test = base.extend({
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
credentials: async ({}, use, testInfo) => {
|
||||
await use({
|
||||
username: `user_${testInfo.testId}`,
|
||||
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
|
||||
password: "oETo7MPf0o",
|
||||
});
|
||||
} as CredentialsWithDisplayName);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 983 KiB After Width: | Height: | Size: 985 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
@@ -25,7 +25,7 @@ import { type HomeserverContainer, type StartedHomeserverContainer } from "./Hom
|
||||
import { type StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
|
||||
import { Api, ClientServerApi, type Verb } from "../plugins/utils/api.ts";
|
||||
|
||||
const TAG = "develop@sha256:32ee365ad97dde86033e8a33e143048167271299e4c727413f3cdff48c65f8d9";
|
||||
const TAG = "develop@sha256:fa3090607a5e07a4ff245247aa3b598c6bbcff9231fd89a558de97c37adbd744";
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
server_name: "localhost",
|
||||
|
||||
@@ -288,6 +288,7 @@
|
||||
@import "./views/rooms/_IRCLayout.pcss";
|
||||
@import "./views/rooms/_InvitedIconView.pcss";
|
||||
@import "./views/rooms/_JumpToBottomButton.pcss";
|
||||
@import "./views/rooms/_LegacyRoomListHeader.pcss";
|
||||
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
||||
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
||||
@import "./views/rooms/_LiveContentSummary.pcss";
|
||||
@@ -312,7 +313,6 @@
|
||||
@import "./views/rooms/_RoomInfoLine.pcss";
|
||||
@import "./views/rooms/_RoomKnocksBar.pcss";
|
||||
@import "./views/rooms/_RoomList.pcss";
|
||||
@import "./views/rooms/_RoomListHeader.pcss";
|
||||
@import "./views/rooms/_RoomPreviewBar.pcss";
|
||||
@import "./views/rooms/_RoomPreviewCard.pcss";
|
||||
@import "./views/rooms/_RoomSearchAuxPanel.pcss";
|
||||
|
||||
@@ -10,8 +10,9 @@ Please see LICENSE files in the repository root for full details.
|
||||
--cpd-separator-inset: calc(50% - (var(--width) / 2));
|
||||
--cpd-separator-spacing: var(--cpd-space-8x);
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol";
|
||||
text-align: center;
|
||||
color: var(--cpd-color-text-primary);
|
||||
width: 100%;
|
||||
|
||||
@@ -113,7 +113,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& + .mx_RoomListHeader {
|
||||
& + .mx_LegacyRoomListHeader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomListHeader:first-child {
|
||||
.mx_LegacyRoomListHeader:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -365,7 +365,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
Note the top fade is much smaller because the spaces start close to the top,
|
||||
so otherwise a large gradient suddenly appears when you scroll down.
|
||||
*/
|
||||
mask-image: linear-gradient(to bottom, transparent, black 16px),
|
||||
mask-image:
|
||||
linear-gradient(to bottom, transparent, black 16px),
|
||||
linear-gradient(
|
||||
to top,
|
||||
transparent,
|
||||
|
||||
@@ -16,7 +16,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
opacity: 0.6;
|
||||
background-image: radial-gradient(
|
||||
background-image:
|
||||
radial-gradient(
|
||||
53.85% 66.75% at 87.55% 0%,
|
||||
hsla(250deg, 76%, 71%, 0.261) 0%,
|
||||
hsla(250deg, 100%, 88%, 0) 100%
|
||||
|
||||
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_RoomListHeader {
|
||||
.mx_LegacyRoomListHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomListHeader_contextLessTitle,
|
||||
.mx_RoomListHeader_contextMenuButton {
|
||||
.mx_LegacyRoomListHeader_contextLessTitle,
|
||||
.mx_LegacyRoomListHeader_contextMenuButton {
|
||||
font: var(--cpd-font-heading-sm-semibold);
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
padding: 1px 24px 1px 4px;
|
||||
@@ -24,7 +24,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mx_RoomListHeader_contextMenuButton {
|
||||
.mx_LegacyRoomListHeader_contextMenuButton {
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
@@ -54,7 +54,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomListHeader_plusButton {
|
||||
.mx_LegacyRoomListHeader_plusButton {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
@@ -88,21 +88,21 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomListHeader_iconInvite::before {
|
||||
.mx_LegacyRoomListHeader_iconInvite::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/invite.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconStartChat::before {
|
||||
.mx_LegacyRoomListHeader_iconStartChat::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconNewRoom::before {
|
||||
.mx_LegacyRoomListHeader_iconNewRoom::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconNewVideoRoom::before {
|
||||
.mx_LegacyRoomListHeader_iconNewVideoRoom::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconExplore::before {
|
||||
.mx_LegacyRoomListHeader_iconExplore::before {
|
||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
|
||||
}
|
||||
.mx_RoomListHeader_iconPlus::before {
|
||||
.mx_LegacyRoomListHeader_iconPlus::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg");
|
||||
}
|
||||
@@ -309,11 +309,8 @@ body {
|
||||
|
||||
/* Splash Page Gradient */
|
||||
.mx_SplashPage::before {
|
||||
background-image: radial-gradient(
|
||||
53.85% 66.75% at 87.55% 0%,
|
||||
hsla(0deg, 0%, 11%, 0.15) 0%,
|
||||
hsla(250deg, 100%, 88%, 0) 100%
|
||||
),
|
||||
background-image:
|
||||
radial-gradient(53.85% 66.75% at 87.55% 0%, hsla(0deg, 0%, 11%, 0.15) 0%, hsla(250deg, 100%, 88%, 0) 100%),
|
||||
radial-gradient(41.93% 41.93% at 0% 0%, hsla(0deg, 0%, 38%, 0.28) 0%, hsla(250deg, 100%, 88%, 0) 100%),
|
||||
radial-gradient(100% 100% at 0% 0%, hsla(250deg, 100%, 88%, 0.3) 0%, hsla(0deg, 100%, 86%, 0) 100%),
|
||||
radial-gradient(106.35% 96.26% at 100% 0%, hsla(25deg, 100%, 88%, 0.4) 0%, hsla(167deg, 76%, 82%, 0) 100%) !important;
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
||||
digits in flowed text to stand out.
|
||||
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
||||
$font-family: "Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
|
||||
sans-serif, "Noto Color Emoji";
|
||||
$font-family:
|
||||
"Nunito", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
|
||||
"Noto Color Emoji";
|
||||
|
||||
$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
|
||||
monospace, "Noto Color Emoji";
|
||||
$monospace-font-family:
|
||||
"Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
|
||||
"Noto Color Emoji";
|
||||
|
||||
/* unified palette */
|
||||
/* try to use these colors when possible */
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
||||
digits in flowed text to stand out.
|
||||
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
||||
$font-family: "Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica",
|
||||
sans-serif, "Noto Color Emoji";
|
||||
$font-family:
|
||||
"Inter", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
|
||||
"Noto Color Emoji";
|
||||
|
||||
$monospace-font-family: "Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier",
|
||||
monospace, "Noto Color Emoji";
|
||||
$monospace-font-family:
|
||||
"Inconsolata", var(--emoji-font-family), "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace,
|
||||
"Noto Color Emoji";
|
||||
|
||||
/* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 */
|
||||
/* ******************** */
|
||||
|
||||
@@ -48,6 +48,11 @@ import { asyncSomeParallel } from "./utils/arrays.ts";
|
||||
|
||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
// Unfortunately named account data key used by Element X to indicate that the user
|
||||
// has chosen to disable server side key backups. We need to set and honour this
|
||||
// to prevent Element X from automatically turning key backup back on.
|
||||
const BACKUP_DISABLED_ACCOUNT_DATA_KEY = "m.org.matrix.custom.backup_disabled";
|
||||
|
||||
const logger = baseLogger.getChild("DeviceListener:");
|
||||
|
||||
export default class DeviceListener {
|
||||
@@ -323,9 +328,11 @@ export default class DeviceListener {
|
||||
logger.info("Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast");
|
||||
showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC);
|
||||
} else if (defaultKeyId === null) {
|
||||
// the user just hasn't set up 4S yet: prompt them to do so
|
||||
logger.info("No default 4S key: showing SET_UP_RECOVERY toast");
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
|
||||
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage)
|
||||
const disabledEvent = cli.getAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY);
|
||||
if (!disabledEvent?.getContent().disabled) {
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
|
||||
}
|
||||
} else {
|
||||
// some other condition... yikes! Show the 'set up encryption' toast: this is what we previously did
|
||||
// in 'other' situations. Possibly we should consider prompting for a full reset in this case?
|
||||
|
||||
@@ -71,7 +71,7 @@ export interface IConfigOptions {
|
||||
url: string; // download url
|
||||
url_macos?: string;
|
||||
url_win64?: string;
|
||||
url_win32?: string;
|
||||
url_win64arm?: string;
|
||||
url_linux?: string;
|
||||
};
|
||||
mobile_builds: {
|
||||
|
||||
@@ -59,7 +59,7 @@ export const DEFAULTS: DeepReadonly<IConfigOptions> = {
|
||||
url: "https://element.io/download",
|
||||
url_macos: "https://packages.element.io/desktop/install/macos/Element.dmg",
|
||||
url_win64: "https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe",
|
||||
url_win32: "https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe",
|
||||
url_win64arm: "https://packages.element.io/desktop/install/win32/arm64/Element%20Setup.exe",
|
||||
url_linux: "https://element.io/download#linux",
|
||||
},
|
||||
mobile_builds: {
|
||||
|
||||
@@ -80,9 +80,9 @@ const MobileAppLinks: React.FC<{
|
||||
const DesktopAppLinks: React.FC<{
|
||||
macOsUrl?: string;
|
||||
win64Url?: string;
|
||||
win32Url?: string;
|
||||
win64ArmUrl?: string;
|
||||
linuxUrl?: string;
|
||||
}> = ({ macOsUrl, win64Url, win32Url, linuxUrl }) => {
|
||||
}> = ({ macOsUrl, win64Url, win64ArmUrl, linuxUrl }) => {
|
||||
return (
|
||||
<Flex gap="var(--cpd-space-4x)">
|
||||
{macOsUrl && (
|
||||
@@ -92,12 +92,12 @@ const DesktopAppLinks: React.FC<{
|
||||
)}
|
||||
{win64Url && (
|
||||
<Button as="a" href={win64Url} kind="secondary" Icon={MicrosoftIcon}>
|
||||
{_t("incompatible_browser|windows", { bits: "64" })}
|
||||
{_t("incompatible_browser|windows_64bit")}
|
||||
</Button>
|
||||
)}
|
||||
{win32Url && (
|
||||
<Button as="a" href={win32Url} kind="secondary" Icon={MicrosoftIcon}>
|
||||
{_t("incompatible_browser|windows", { bits: "32" })}
|
||||
{win64ArmUrl && (
|
||||
<Button as="a" href={win64ArmUrl} kind="secondary" Icon={MicrosoftIcon}>
|
||||
{_t("incompatible_browser|windows_arm_64bit")}
|
||||
</Button>
|
||||
)}
|
||||
{linuxUrl && (
|
||||
@@ -127,7 +127,7 @@ export const UnsupportedBrowserView: React.FC<{
|
||||
config.desktop_builds?.available &&
|
||||
(config.desktop_builds?.url_macos ||
|
||||
config.desktop_builds?.url_win64 ||
|
||||
config.desktop_builds?.url_win32 ||
|
||||
config.desktop_builds?.url_win64arm ||
|
||||
config.desktop_builds?.url_linux);
|
||||
const hasMobileBuilds = Boolean(
|
||||
config.mobile_builds?.ios || config.mobile_builds?.android || config.mobile_builds?.fdroid,
|
||||
@@ -157,7 +157,7 @@ export const UnsupportedBrowserView: React.FC<{
|
||||
<DesktopAppLinks
|
||||
macOsUrl={config.desktop_builds?.url_macos}
|
||||
win64Url={config.desktop_builds?.url_win64}
|
||||
win32Url={config.desktop_builds?.url_win32}
|
||||
win64ArmUrl={config.desktop_builds?.url_win64arm}
|
||||
linuxUrl={config.desktop_builds?.url_linux}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -27,7 +27,7 @@ import EmbeddedPage from "./EmbeddedPage";
|
||||
|
||||
const onClickSendDm = (ev: ButtonEvent): void => {
|
||||
PosthogTrackers.trackInteraction("WebHomeCreateChatButton", ev);
|
||||
dis.dispatch({ action: "view_create_chat" });
|
||||
dis.dispatch({ action: Action.CreateChat });
|
||||
};
|
||||
|
||||
const onClickExplore = (ev: ButtonEvent): void => {
|
||||
@@ -37,7 +37,7 @@ const onClickExplore = (ev: ButtonEvent): void => {
|
||||
|
||||
const onClickNewRoom = (ev: ButtonEvent): void => {
|
||||
PosthogTrackers.trackInteraction("WebHomeCreateRoomButton", ev);
|
||||
dis.dispatch({ action: "view_create_room" });
|
||||
dis.dispatch({ action: Action.CreateRoom });
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { MetaSpace, type SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/sp
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
import { type IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
|
||||
import RoomListHeader from "../views/rooms/RoomListHeader";
|
||||
import LegacyRoomListHeader from "../views/rooms/LegacyRoomListHeader";
|
||||
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
@@ -415,7 +415,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
<div className="mx_LeftPanel_roomListContainer">
|
||||
{shouldShowComponent(UIComponent.FilterContainer) && this.renderSearchDialExplore()}
|
||||
{this.renderBreadcrumbs()}
|
||||
{!this.props.isMinimized && <RoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
|
||||
{!this.props.isMinimized && <LegacyRoomListHeader onVisibilityChange={this.refreshStickyHeaders} />}
|
||||
<nav className="mx_LeftPanel_roomListWrapper" aria-label={_t("common|rooms")}>
|
||||
<div
|
||||
className={roomListClasses}
|
||||
|
||||
@@ -144,7 +144,7 @@ const AUTH_SCREENS = ["register", "mobile_register", "login", "forgot_password",
|
||||
// Actions that are redirected through the onboarding process prior to being
|
||||
// re-dispatched. NOTE: some actions are non-trivial and would require
|
||||
// re-factoring to be included in this list in future.
|
||||
const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, "view_create_chat", "view_create_room"];
|
||||
const ONBOARDING_FLOW_STARTERS = [Action.ViewUserSettings, Action.CreateChat, Action.CreateRoom];
|
||||
|
||||
interface IScreen {
|
||||
screen: string;
|
||||
@@ -616,7 +616,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
|
||||
// Start the onboarding process for certain actions
|
||||
if (MatrixClientPeg.get()?.isGuest() && ONBOARDING_FLOW_STARTERS.includes(payload.action)) {
|
||||
if (
|
||||
MatrixClientPeg.get()?.isGuest() &&
|
||||
ONBOARDING_FLOW_STARTERS.includes(payload.action as unknown as Action)
|
||||
) {
|
||||
// This will cause `payload` to be dispatched later, once a
|
||||
// sync has reached the "prepared" state. Setting a matrix ID
|
||||
// will cause a full login and sync and finally the deferred
|
||||
@@ -785,7 +788,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
this.viewSomethingBehindModal();
|
||||
break;
|
||||
}
|
||||
case "view_create_room":
|
||||
case Action.CreateRoom:
|
||||
this.createRoom(payload.public, payload.defaultName, payload.type);
|
||||
|
||||
// View the welcome or home page if we need something to look at
|
||||
@@ -816,7 +819,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
case Action.ViewStartChatOrReuse:
|
||||
this.chatCreateOrReuse(payload.user_id);
|
||||
break;
|
||||
case "view_create_chat":
|
||||
case Action.CreateChat:
|
||||
showStartChatInviteDialog(payload.initialText || "");
|
||||
|
||||
// View the welcome or home page if we need something to look at
|
||||
@@ -1758,11 +1761,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
} else if (screen === "new") {
|
||||
dis.dispatch({
|
||||
action: "view_create_room",
|
||||
action: Action.CreateRoom,
|
||||
});
|
||||
} else if (screen === "dm") {
|
||||
dis.dispatch({
|
||||
action: "view_create_chat",
|
||||
action: Action.CreateChat,
|
||||
});
|
||||
} else if (screen === "settings") {
|
||||
dis.fire(Action.ViewUserSettings);
|
||||
|
||||
@@ -118,8 +118,6 @@ import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages";
|
||||
import { LargeLoader } from "./LargeLoader";
|
||||
import { isVideoRoom } from "../../utils/video-rooms";
|
||||
import { SDKContext } from "../../contexts/SDKContext";
|
||||
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||
import { type Call } from "../../models/Call";
|
||||
import { RoomSearchView } from "./RoomSearchView";
|
||||
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
|
||||
import VoipUserMapper from "../../VoipUserMapper";
|
||||
@@ -190,7 +188,6 @@ export interface IRoomState {
|
||||
*/
|
||||
search?: SearchInfo;
|
||||
callState?: CallState;
|
||||
activeCall: Call | null;
|
||||
canPeek: boolean;
|
||||
canSelfRedact: boolean;
|
||||
showApps: boolean;
|
||||
@@ -401,7 +398,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
membersLoaded: !llMembers,
|
||||
numUnreadMessages: 0,
|
||||
callState: undefined,
|
||||
activeCall: null,
|
||||
canPeek: false,
|
||||
canSelfRedact: false,
|
||||
showApps: false,
|
||||
@@ -577,7 +573,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
|
||||
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
|
||||
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
|
||||
activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
|
||||
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
|
||||
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
|
||||
};
|
||||
@@ -727,23 +722,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
});
|
||||
};
|
||||
|
||||
private onConnectedCalls = (): void => {
|
||||
if (this.state.roomId === undefined) return;
|
||||
const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
|
||||
if (activeCall === null) {
|
||||
// We disconnected from the call, so stop viewing it
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>(
|
||||
{
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.state.roomId,
|
||||
view_call: false,
|
||||
metricsTrigger: undefined,
|
||||
},
|
||||
true,
|
||||
); // Synchronous so that CallView disappears immediately
|
||||
}
|
||||
|
||||
this.setState({ activeCall });
|
||||
private onCallClose = (): void => {
|
||||
// Stop viewing the call
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>(
|
||||
{
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.state.roomId,
|
||||
view_call: false,
|
||||
metricsTrigger: undefined,
|
||||
},
|
||||
true,
|
||||
); // Synchronous so that CallView disappears immediately
|
||||
};
|
||||
|
||||
private getRoomId = (): string | undefined => {
|
||||
@@ -900,8 +889,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
||||
this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
|
||||
CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
|
||||
|
||||
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
|
||||
|
||||
this.settingWatchers = [
|
||||
@@ -1027,7 +1014,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
);
|
||||
}
|
||||
|
||||
CallStore.instance.off(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
|
||||
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
// cancel any pending calls to the throttled updated
|
||||
@@ -2562,9 +2548,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
<CallView
|
||||
room={this.state.room}
|
||||
resizing={this.state.resizing}
|
||||
waitForCall={isVideoRoom(this.state.room)}
|
||||
skipLobby={this.context.roomViewStore.skipCallLobby() ?? false}
|
||||
role="main"
|
||||
onClose={this.onCallClose}
|
||||
/>
|
||||
{previewBar}
|
||||
</>
|
||||
|
||||
@@ -954,7 +954,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||
className="mx_SpotlightDialog_createRoom"
|
||||
onClick={() =>
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_create_room",
|
||||
action: Action.CreateRoom,
|
||||
public: true,
|
||||
defaultName: capitalize(trimmedQuery),
|
||||
})
|
||||
|
||||
@@ -126,10 +126,6 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
|
||||
return [_t("action|leave"), "danger", disconnect];
|
||||
case ConnectionState.Disconnecting:
|
||||
return [_t("action|leave"), "danger", null];
|
||||
case ConnectionState.Connecting:
|
||||
case ConnectionState.Lobby:
|
||||
case ConnectionState.WidgetLoading:
|
||||
return [_t("action|join"), "primary", null];
|
||||
}
|
||||
}, [connectionState, connect, disconnect]);
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
defaultDispatcher.dispatch({ action: Action.CreateChat });
|
||||
PosthogTrackers.trackInteraction(
|
||||
"WebRoomListRoomsSublistPlusMenuCreateChatItem",
|
||||
e,
|
||||
@@ -194,7 +194,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||
<AccessibleButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={(e) => {
|
||||
dispatcher.dispatch({ action: "view_create_chat" });
|
||||
dispatcher.dispatch({ action: Action.CreateChat });
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateChatItem", e);
|
||||
}}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
@@ -305,7 +305,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
defaultDispatcher.dispatch({ action: Action.CreateRoom });
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e);
|
||||
}}
|
||||
/>
|
||||
@@ -318,7 +318,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
e.stopPropagation();
|
||||
closeMenu();
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_create_room",
|
||||
action: Action.CreateRoom,
|
||||
type: elementCallVideoRoomsEnabled
|
||||
? RoomType.UnstableCall
|
||||
: RoomType.ElementVideo,
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventType, RoomType, type Room, RoomEvent, ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientEvent, EventType, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
|
||||
@@ -38,10 +38,10 @@ import {
|
||||
} from "../../../utils/space";
|
||||
import {
|
||||
ChevronFace,
|
||||
ContextMenuTooltipButton,
|
||||
useContextMenu,
|
||||
type MenuProps,
|
||||
ContextMenuButton,
|
||||
ContextMenuTooltipButton,
|
||||
type MenuProps,
|
||||
useContextMenu,
|
||||
} from "../../structures/ContextMenu";
|
||||
import { BetaPill } from "../beta/BetaCard";
|
||||
import IconizedContextMenu, {
|
||||
@@ -108,7 +108,7 @@ interface IProps {
|
||||
onVisibilityChange?(): void;
|
||||
}
|
||||
|
||||
const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const [mainMenuDisplayed, mainMenuHandle, openMainMenu, closeMainMenu] = useContextMenu<HTMLDivElement>();
|
||||
const [plusMenuDisplayed, plusMenuHandle, openPlusMenu, closePlusMenu] = useContextMenu<HTMLDivElement>();
|
||||
@@ -178,7 +178,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
inviteOption = (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("action|invite")}
|
||||
iconClassName="mx_RoomListHeader_iconInvite"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconInvite"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -194,7 +194,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
newRoomOptions = (
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconNewRoom"
|
||||
label={_t("action|new_room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -206,7 +206,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
/>
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom"
|
||||
label={_t("action|new_video_room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -236,7 +236,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
{newRoomOptions}
|
||||
<IconizedContextMenuOption
|
||||
label={_t("action|explore_rooms")}
|
||||
iconClassName="mx_RoomListHeader_iconExplore"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -251,7 +251,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("action|add_existing_room")}
|
||||
iconClassName="mx_RoomListHeader_iconPlus"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconPlus"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -264,7 +264,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
{canCreateSpaces && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("room_list|add_space_label")}
|
||||
iconClassName="mx_RoomListHeader_iconPlus"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconPlus"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -289,22 +289,22 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("action|start_new_chat")}
|
||||
iconClassName="mx_RoomListHeader_iconStartChat"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconStartChat"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: "view_create_chat" });
|
||||
defaultDispatcher.dispatch({ action: Action.CreateChat });
|
||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e);
|
||||
closePlusMenu();
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("action|new_room")}
|
||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({ action: "view_create_room" });
|
||||
defaultDispatcher.dispatch({ action: Action.CreateRoom });
|
||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
|
||||
closePlusMenu();
|
||||
}}
|
||||
@@ -312,12 +312,12 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("action|new_video_room")}
|
||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_create_room",
|
||||
action: Action.CreateRoom,
|
||||
type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo,
|
||||
});
|
||||
closePlusMenu();
|
||||
@@ -333,7 +333,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
joinRoomOpt = (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("room_list|join_public_room_label")}
|
||||
iconClassName="mx_RoomListHeader_iconExplore"
|
||||
iconClassName="mx_LegacyRoomListHeader_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -378,13 +378,13 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let contextMenuButton: JSX.Element = <div className="mx_RoomListHeader_contextLessTitle">{title}</div>;
|
||||
let contextMenuButton: JSX.Element = <div className="mx_LegacyRoomListHeader_contextLessTitle">{title}</div>;
|
||||
if (canShowMainMenu) {
|
||||
const commonProps = {
|
||||
ref: mainMenuHandle,
|
||||
onClick: openMainMenu,
|
||||
isExpanded: mainMenuDisplayed,
|
||||
className: "mx_RoomListHeader_contextMenuButton",
|
||||
className: "mx_LegacyRoomListHeader_contextMenuButton",
|
||||
children: title,
|
||||
};
|
||||
|
||||
@@ -401,7 +401,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="mx_RoomListHeader" aria-label={_t("room|context_menu|title")}>
|
||||
<aside className="mx_LegacyRoomListHeader" aria-label={_t("room|context_menu|title")}>
|
||||
{contextMenuButton}
|
||||
{pendingActionSummary ? (
|
||||
<Tooltip label={pendingActionSummary} isTriggerInteractive={false}>
|
||||
@@ -413,7 +413,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
ref={plusMenuHandle}
|
||||
onClick={openPlusMenu}
|
||||
isExpanded={plusMenuDisplayed}
|
||||
className="mx_RoomListHeader_plusButton"
|
||||
className="mx_LegacyRoomListHeader_plusButton"
|
||||
title={_t("action|add")}
|
||||
/>
|
||||
)}
|
||||
@@ -423,4 +423,4 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomListHeader;
|
||||
export default LegacyRoomListHeader;
|
||||
@@ -27,18 +27,6 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
|
||||
text = _t("common|video");
|
||||
active = false;
|
||||
break;
|
||||
case ConnectionState.WidgetLoading:
|
||||
text = _t("common|loading");
|
||||
active = false;
|
||||
break;
|
||||
case ConnectionState.Lobby:
|
||||
text = _t("common|lobby");
|
||||
active = false;
|
||||
break;
|
||||
case ConnectionState.Connecting:
|
||||
text = _t("room|joining");
|
||||
active = true;
|
||||
break;
|
||||
case ConnectionState.Connected:
|
||||
case ConnectionState.Disconnecting:
|
||||
text = _t("common|joined");
|
||||
|
||||
@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type FC, useContext, useEffect, type AriaRole, useCallback } from "react";
|
||||
|
||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { type Call, ConnectionState, ElementCall } from "../../../models/Call";
|
||||
import { useCall } from "../../../hooks/useCall";
|
||||
import { type Call, CallEvent } from "../../../models/Call";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import AppTile from "../elements/AppTile";
|
||||
import { CallStore } from "../../../stores/CallStore";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import { useCall } from "../../../hooks/useCall";
|
||||
|
||||
interface JoinCallViewProps {
|
||||
room: Room;
|
||||
@@ -22,10 +23,12 @@ interface JoinCallViewProps {
|
||||
call: Call;
|
||||
skipLobby?: boolean;
|
||||
role?: AriaRole;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role }) => {
|
||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role, onClose }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
useTypedEventEmitter(call, CallEvent.Close, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
// We'll take this opportunity to tidy up our room state
|
||||
@@ -38,17 +41,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
||||
call.widget.data.skipLobby = skipLobby;
|
||||
}, [call.widget, skipLobby]);
|
||||
|
||||
useEffect(() => {
|
||||
if (call.connectionState === ConnectionState.Disconnected) {
|
||||
// immediately start the call
|
||||
// (this will start the lobby view in the widget and connect to all required widget events)
|
||||
call.start();
|
||||
}
|
||||
return (): void => {
|
||||
// If we are connected the widget is sticky and we do not want to destroy the call.
|
||||
if (!call.connected) call.destroy();
|
||||
};
|
||||
}, [call]);
|
||||
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
|
||||
// The stickyPromise has to resolve before the widget actually becomes sticky.
|
||||
// We only let the widget become sticky after disconnecting all other active calls.
|
||||
@@ -57,6 +49,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
||||
);
|
||||
await Promise.all(calls.map(async (call) => await call.disconnect()));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx_CallView" role={role}>
|
||||
<AppTile
|
||||
@@ -76,26 +69,27 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
||||
interface CallViewProps {
|
||||
room: Room;
|
||||
resizing: boolean;
|
||||
/**
|
||||
* If true, the view will be blank until a call appears. Otherwise, the join
|
||||
* button will create a call if there isn't already one.
|
||||
*/
|
||||
waitForCall: boolean;
|
||||
skipLobby?: boolean;
|
||||
role?: AriaRole;
|
||||
/**
|
||||
* Callback for when the user closes the call.
|
||||
*/
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, skipLobby, role }) => {
|
||||
export const CallView: FC<CallViewProps> = ({ room, resizing, skipLobby, role, onClose }) => {
|
||||
const call = useCall(room.roomId);
|
||||
|
||||
useEffect(() => {
|
||||
if (call === null && !waitForCall) {
|
||||
ElementCall.create(room, skipLobby);
|
||||
}
|
||||
}, [call, room, skipLobby, waitForCall]);
|
||||
if (call === null) {
|
||||
return null;
|
||||
} else {
|
||||
return <JoinCallView room={room} resizing={resizing} call={call} skipLobby={skipLobby} role={role} />;
|
||||
}
|
||||
return (
|
||||
call && (
|
||||
<JoinCallView
|
||||
room={room}
|
||||
resizing={resizing}
|
||||
call={call}
|
||||
skipLobby={skipLobby}
|
||||
role={role}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -70,7 +70,6 @@ const RoomContext = createContext<
|
||||
threadId: undefined,
|
||||
liveTimeline: undefined,
|
||||
narrow: false,
|
||||
activeCall: null,
|
||||
msc3946ProcessDynamicPredecessor: false,
|
||||
canAskToJoin: false,
|
||||
promptAskToJoin: false,
|
||||
|
||||
@@ -343,7 +343,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
|
||||
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
||||
} else if (opts.roomType === RoomType.UnstableCall) {
|
||||
// Set up this video room with an Element call
|
||||
await ElementCall.create(await room);
|
||||
ElementCall.create(await room);
|
||||
|
||||
// Reset our power level back to admin so that the call becomes immutable
|
||||
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
||||
|
||||
@@ -365,4 +365,14 @@ export enum Action {
|
||||
* Opens right panel room summary and focuses the search input
|
||||
*/
|
||||
FocusMessageSearch = "focus_search",
|
||||
|
||||
/**
|
||||
* Open the direct message dialog
|
||||
*/
|
||||
CreateChat = "view_create_chat",
|
||||
|
||||
/**
|
||||
* Open the create room dialog
|
||||
*/
|
||||
CreateRoom = "view_create_room",
|
||||
}
|
||||
|
||||
@@ -75,9 +75,5 @@ export const useFull = (call: Call | null): boolean => {
|
||||
|
||||
export const useJoinCallButtonDisabledTooltip = (call: Call | null): string | null => {
|
||||
const isFull = useFull(call);
|
||||
const state = useConnectionState(call);
|
||||
|
||||
if (state === ConnectionState.Connecting) return _t("voip|join_button_tooltip_connecting");
|
||||
if (isFull) return _t("voip|join_button_tooltip_call_full");
|
||||
return null;
|
||||
return isFull ? _t("voip|join_button_tooltip_call_full") : null;
|
||||
};
|
||||
|
||||
@@ -712,6 +712,44 @@
|
||||
"category_room": "Stafell",
|
||||
"caution_colon": "Rhybudd:",
|
||||
"client_versions": "Fersiynau Cleient",
|
||||
"crypto": {
|
||||
"4s_public_key_in_account_data": "mewn data cyfrif",
|
||||
"4s_public_key_not_in_account_data": "heb ei ganfod",
|
||||
"4s_public_key_status": "Allwedd gyhoeddus storio gyfrinachol:",
|
||||
"backup_key_cached": "wedi'i storio'n lleol",
|
||||
"backup_key_cached_status": "Allwedd wrth gefn wedi'i storio:",
|
||||
"backup_key_not_stored": "heb ei storio",
|
||||
"backup_key_stored": "mewn storfa gyfrinachol",
|
||||
"backup_key_stored_status": "Allwedd wrth gefn wedi'i storio:",
|
||||
"backup_key_unexpected_type": "math annisgwyl",
|
||||
"backup_key_well_formed": "wedi'i ffurfio'n dda",
|
||||
"cross_signing": "Traws-arwyddo",
|
||||
"cross_signing_cached": "wedi'i storio'n lleol",
|
||||
"cross_signing_not_ready": "Nid yw traws-arwyddo wedi'i osod.",
|
||||
"cross_signing_private_keys_in_storage": "mewn storfa gyfrinachol",
|
||||
"cross_signing_private_keys_in_storage_status": "Traws-lofnodi allweddi preifat:",
|
||||
"cross_signing_private_keys_not_in_storage": "heb ei ganfod yn y storfa",
|
||||
"cross_signing_public_keys_on_device": "yn y cof",
|
||||
"cross_signing_public_keys_on_device_status": "Traws-lofnodi allweddi cyhoeddus:",
|
||||
"cross_signing_ready": "Mae croes-arwyddo yn barod i'w ddefnyddio.",
|
||||
"cross_signing_status": "Statws traws-arwyddo:",
|
||||
"cross_signing_untrusted": "Mae gan eich cyfrif hunaniaeth traws-lofnodi mewn storfa gyfrinachol, ond nid yw'r sesiwn hon yn ymddiried ynddo eto.",
|
||||
"crypto_not_available": "Nid yw modiwl cryptograffig ar gael",
|
||||
"key_backup_active_version": "Fersiwn wrth gefn gweithredol:",
|
||||
"key_backup_active_version_none": "Dim",
|
||||
"key_backup_inactive_warning": "Nid yw'ch allweddi'n cael eu gwneud wrth gefn o'r sesiwn hon.",
|
||||
"key_backup_latest_version": "Fersiwn wrth gefn diweddaraf ar y gweinydd:",
|
||||
"key_storage": "Storio Allwedd",
|
||||
"master_private_key_cached_status": "Prif allwedd breifat:",
|
||||
"not_found": "heb ei ganfod",
|
||||
"not_found_locally": "heb ei ganfod yn lleol",
|
||||
"secret_storage_not_ready": "ddim yn barod",
|
||||
"secret_storage_ready": "barod",
|
||||
"secret_storage_status": "Storio cyfrinachol:",
|
||||
"self_signing_private_key_cached_status": "Allwedd breifat hunan-arwyddo:",
|
||||
"title": "Amgryptio o ben i ben",
|
||||
"user_signing_private_key_cached_status": "Defnyddiwr yn arwyddo allwedd breifat:"
|
||||
},
|
||||
"developer_mode": "Modd datblygwr",
|
||||
"developer_tools": "Offer Datblygwr",
|
||||
"edit_setting": "Golygu gosodiad",
|
||||
@@ -989,8 +1027,11 @@
|
||||
"waiting_other_user": "Aros i %(displayName)s wirio…"
|
||||
},
|
||||
"verification_requested_toast_title": "Gofynnwyd am ddilysiad",
|
||||
"verified_identity_changed": "Mae hunaniaeth %(displayName)s ( <b>%(userId)s</b> ) a ddilyswyd wedi newid. <a>Dysgwch fwy</a>",
|
||||
"verified_identity_changed_no_displayname": "Mae hunaniaeth ddilysedig <b>%(userId)s</b> wedi newid. <a>Dysgwch fwy</a>",
|
||||
"verify_toast_description": "Efallai na fydd defnyddwyr eraill yn ymddiried ynddo",
|
||||
"verify_toast_title": "Gwiriwch y sesiwn hon"
|
||||
"verify_toast_title": "Gwiriwch y sesiwn hon",
|
||||
"withdraw_verification_action": "Tynnu'r dilysiad yn ôl"
|
||||
},
|
||||
"error": {
|
||||
"admin_contact": "<a>Cysylltwch â gweinyddwr eich gwasanaeth</a> i barhau i ddefnyddio'r gwasanaeth hwn.",
|
||||
@@ -1399,6 +1440,7 @@
|
||||
"location_share_live_description": "Gweithredu dros dro. Mae lleoliadau'n parhau yn hanes ystafelloedd.",
|
||||
"mjolnir": "Ffyrdd newydd o anwybyddu pobl",
|
||||
"msc3531_hide_messages_pending_moderation": "Gadael i safonwyr guddio negeseuon tra'n aros i'w safoni.",
|
||||
"new_room_list": "Galluogi rhestr ystafelloedd newydd",
|
||||
"notification_settings": "Gosodiadau Hysbysu Newydd",
|
||||
"notification_settings_beta_caption": "Cyflwyno ffordd symlach o newid eich gosodiadau hysbysu. Addaswch eich %(brand)s, yn union fel y dymunwch.",
|
||||
"notification_settings_beta_title": "Gosodiadau Hysbysiadau",
|
||||
@@ -2279,6 +2321,25 @@
|
||||
"enable_markdown": "Galluogi Markdown",
|
||||
"enable_markdown_description": "Dechreuwch negeseuon gyda <code>/plain</code> i'w hanfon heb eu marcio i lawr.",
|
||||
"encryption": {
|
||||
"advanced": {
|
||||
"breadcrumb_first_description": "Bydd manylion eich cyfrif, eich cysylltiadau, eich dewisiadau a'ch rhestr sgwrsio yn cael eu cadw",
|
||||
"breadcrumb_page": "Ailosod amgryptio",
|
||||
"breadcrumb_second_description": "Byddwch yn colli unrhyw hanes neges sydd wedi'i storio ar y gweinydd yn unig",
|
||||
"breadcrumb_third_description": "Bydd angen i chi wirio'ch holl ddyfeisiau a chysylltiadau presennol eto",
|
||||
"breadcrumb_title": "Ydych chi'n siŵr eich bod am ailosod eich hunaniaeth?",
|
||||
"breadcrumb_title_forgot": "Wedi anghofio eich allwedd adfer? Bydd angen i chi ailosod eich hunaniaeth.",
|
||||
"breadcrumb_warning": "Gwnewch hyn dim ond os ydych chi'n credu bod eich cyfrif wedi'i beryglu.",
|
||||
"details_title": "Manylion amgryptio",
|
||||
"export_keys": "Allforio allweddi",
|
||||
"import_keys": "Mewnforio bysellau",
|
||||
"other_people_device_description": "Yn ragosodedig mewn ystafelloedd wedi'u hamgryptio, peidiwch ag anfon negeseuon wedi'u hamgryptio at unrhyw un nes i chi eu gwirio",
|
||||
"other_people_device_label": "Peidiwch byth ag anfon negeseuon wedi'u hamgryptio i ddyfeisiau heb eu gwirio",
|
||||
"other_people_device_title": "Dyfeisiau pobl eraill",
|
||||
"reset_identity": "Ailosod hunaniaeth cryptograffig",
|
||||
"session_id": "ID y sesiwn:",
|
||||
"session_key": "Allwedd sesiwn:",
|
||||
"title": "Uwch"
|
||||
},
|
||||
"device_not_verified_button": "Dilyswch y ddyfais hon",
|
||||
"device_not_verified_description": "Mae angen i chi wirio'r ddyfais hon er mwyn gweld eich gosodiadau amgryptio.",
|
||||
"device_not_verified_title": "Dyfais heb ei gwirio",
|
||||
@@ -2293,6 +2354,7 @@
|
||||
"description": "Adfer eich hunaniaeth cryptograffig a hanes neges gydag allwedd adfer os ydych chi wedi colli'ch holl ddyfeisiau presennol.",
|
||||
"enter_key_error": "Nid yw'r allwedd adfer a roesoch yn gywir.",
|
||||
"enter_recovery_key": "Rhowch allwedd adfer",
|
||||
"forgot_recovery_key": "Wedi anghofio allwedd adfer?",
|
||||
"key_storage_warning": "Nid yw eich storfa allweddi wedi'i chysoni. Cliciwch ar y botwm isod i ddatrys y broblem.",
|
||||
"save_key_description": "Peidiwch â rhannu hwn gyda neb!",
|
||||
"save_key_title": "Allwedd adfer",
|
||||
@@ -3198,6 +3260,7 @@
|
||||
"left_reason": "Gadawodd %(targetName)s yr ystafell: %(reason)s",
|
||||
"no_change": "Ni wnaeth %(senderName)s unrhyw newid",
|
||||
"reject_invite": "Mae %(targetName)s wedi gwrthod y gwahoddiad",
|
||||
"reject_invite_reason": "Mae %(targetName)s wedi gwrthod y gwahoddiad: %(reason)s",
|
||||
"remove_avatar": "Mae %(senderName)s wedi tynnu eu llun proffil",
|
||||
"remove_name": "Mae %(senderName)s wedi tynnu eu henw arddangos (%(oldDisplayName)s)",
|
||||
"set_avatar": "Mae %(senderName)s wedi gosod llun proffil",
|
||||
@@ -3233,6 +3296,10 @@
|
||||
"sent": "Anfonodd %(senderName)s wahoddiad at %(targetDisplayName)s i ymuno â'r ystafell."
|
||||
},
|
||||
"m.room.tombstone": "Mae %(senderDisplayName)s wedi uwchraddio'r ystafell hon.",
|
||||
"m.room.topic": {
|
||||
"changed": "Newidiodd %(senderDisplayName)s y pwnc i \"%(topic)s\".",
|
||||
"removed": "Mae %(senderDisplayName)s wedi dileu'r pwnc."
|
||||
},
|
||||
"m.sticker": "Anfonodd %(senderDisplayName)s sticer.",
|
||||
"m.video": {
|
||||
"error_decrypting": "Gwall wrth ddadgryptio fideo"
|
||||
@@ -3417,6 +3484,7 @@
|
||||
"unban_space_specific": "Gwahardd nhw o bethau penodol y gallaf",
|
||||
"unban_space_warning": "Ni fyddant yn gallu cael mynediad at beth bynnag nad ydych yn weinyddwr iddo.",
|
||||
"unignore_button": "Anwybyddu",
|
||||
"verification_unavailable": "Nid yw dilysu defnyddiwr ar gael",
|
||||
"verify_button": "Dilysu Defnyddiwr",
|
||||
"verify_explainer": "Ar gyfer diogelwch ychwanegol, gwiriwch y defnyddiwr hwn trwy wirio cod un-amser ar eich dwy ddyfais."
|
||||
},
|
||||
|
||||
@@ -495,7 +495,6 @@
|
||||
"legal": "Legal",
|
||||
"light": "Light",
|
||||
"loading": "Loading…",
|
||||
"lobby": "Lobby",
|
||||
"location": "Location",
|
||||
"low_priority": "Low priority",
|
||||
"matrix": "Matrix",
|
||||
@@ -1286,7 +1285,8 @@
|
||||
"use_desktop_heading": "Use %(brand)s Desktop instead",
|
||||
"use_mobile_heading": "Use %(brand)s on mobile instead",
|
||||
"use_mobile_heading_after_desktop": "Or use our mobile app",
|
||||
"windows": "Windows (%(bits)s-bit)"
|
||||
"windows_64bit": "Windows (64-bit)",
|
||||
"windows_arm_64bit": "Windows (ARM 64-bit)"
|
||||
},
|
||||
"info_tooltip_title": "Information",
|
||||
"integration_manager": {
|
||||
@@ -3898,7 +3898,6 @@
|
||||
"input_devices": "Input devices",
|
||||
"jitsi_call": "Jitsi Conference",
|
||||
"join_button_tooltip_call_full": "Sorry — this call is currently full",
|
||||
"join_button_tooltip_connecting": "Connecting",
|
||||
"legacy_call": "Legacy Call",
|
||||
"maximise": "Fill screen",
|
||||
"maximise_call": "Maximise call",
|
||||
|
||||
@@ -77,13 +77,7 @@ const waitForEvent = async (
|
||||
};
|
||||
|
||||
export enum ConnectionState {
|
||||
// Widget related states that are equivalent to disconnected,
|
||||
// but hold additional information about the state of the widget.
|
||||
Lobby = "lobby",
|
||||
WidgetLoading = "widget_loading",
|
||||
Disconnected = "disconnected",
|
||||
|
||||
Connecting = "connecting",
|
||||
Connected = "connected",
|
||||
Disconnecting = "disconnecting",
|
||||
}
|
||||
@@ -100,6 +94,7 @@ export enum CallEvent {
|
||||
ConnectionState = "connection_state",
|
||||
Participants = "participants",
|
||||
Layout = "layout",
|
||||
Close = "close",
|
||||
Destroy = "destroy",
|
||||
}
|
||||
|
||||
@@ -110,6 +105,7 @@ interface CallEventHandlerMap {
|
||||
prevParticipants: Map<RoomMember, Set<string>>,
|
||||
) => void;
|
||||
[CallEvent.Layout]: (layout: Layout) => void;
|
||||
[CallEvent.Close]: () => void;
|
||||
[CallEvent.Destroy]: () => void;
|
||||
}
|
||||
|
||||
@@ -167,6 +163,17 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
this.emit(CallEvent.Participants, value, prevValue);
|
||||
}
|
||||
|
||||
private _presented = false;
|
||||
/**
|
||||
* Whether the call widget is currently being presented in the user interface.
|
||||
*/
|
||||
public get presented(): boolean {
|
||||
return this._presented;
|
||||
}
|
||||
public set presented(value: boolean) {
|
||||
this._presented = value;
|
||||
}
|
||||
|
||||
protected constructor(
|
||||
/**
|
||||
* The widget used to access this call.
|
||||
@@ -177,6 +184,7 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
super();
|
||||
this.widgetUid = WidgetUtils.getWidgetUid(this.widget);
|
||||
this.room = this.client.getRoom(this.roomId)!;
|
||||
WidgetMessagingStore.instance.on(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,8 +229,6 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
* Only call this if the call state is: ConnectionState.Disconnected.
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
this.connectionState = ConnectionState.WidgetLoading;
|
||||
|
||||
const { [MediaDeviceKindEnum.AudioInput]: audioInputs, [MediaDeviceKindEnum.VideoInput]: videoInputs } =
|
||||
(await MediaDeviceHandler.getDevices())!;
|
||||
|
||||
@@ -257,16 +263,9 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
throw new Error(`Failed to bind call widget in room ${this.roomId}: ${e}`);
|
||||
}
|
||||
}
|
||||
this.connectionState = ConnectionState.Connecting;
|
||||
try {
|
||||
await this.performConnection(audioInput, videoInput);
|
||||
} catch (e) {
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
throw e;
|
||||
}
|
||||
await this.performConnection(audioInput, videoInput);
|
||||
|
||||
this.room.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
WidgetMessagingStore.instance.on(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
|
||||
window.addEventListener("beforeunload", this.beforeUnload);
|
||||
this.connectionState = ConnectionState.Connected;
|
||||
}
|
||||
@@ -280,39 +279,54 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
this.connectionState = ConnectionState.Disconnecting;
|
||||
await this.performDisconnection();
|
||||
this.setDisconnected();
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually marks the call as disconnected and cleans up.
|
||||
* Manually marks the call as disconnected.
|
||||
*/
|
||||
public setDisconnected(): void {
|
||||
this.room.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
WidgetMessagingStore.instance.off(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
|
||||
window.removeEventListener("beforeunload", this.beforeUnload);
|
||||
this.messaging = null;
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops further communication with the widget and tells the UI to close.
|
||||
*/
|
||||
protected close(): void {
|
||||
this.messaging = null;
|
||||
this.emit(CallEvent.Close);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all internal timers and tasks to prepare for garbage collection.
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.connected) this.setDisconnected();
|
||||
if (this.connected) {
|
||||
this.setDisconnected();
|
||||
this.close();
|
||||
}
|
||||
WidgetMessagingStore.instance.off(WidgetMessagingStoreEvent.StopMessaging, this.onStopMessaging);
|
||||
this.emit(CallEvent.Destroy);
|
||||
}
|
||||
|
||||
private onMyMembership = async (_room: Room, membership: Membership): Promise<void> => {
|
||||
private readonly onMyMembership = async (_room: Room, membership: Membership): Promise<void> => {
|
||||
if (membership !== KnownMembership.Join) this.setDisconnected();
|
||||
};
|
||||
|
||||
private onStopMessaging = (uid: string): void => {
|
||||
if (uid === this.widgetUid) {
|
||||
private readonly onStopMessaging = (uid: string): void => {
|
||||
if (uid === this.widgetUid && this.connected) {
|
||||
logger.log("The widget died; treating this as a user hangup");
|
||||
this.setDisconnected();
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
private beforeUnload = (): void => this.setDisconnected();
|
||||
private beforeUnload = (): void => {
|
||||
this.setDisconnected();
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
export type { JitsiCallMemberContent };
|
||||
@@ -466,7 +480,6 @@ export class JitsiCall extends Call {
|
||||
audioInput: MediaDeviceInfo | null,
|
||||
videoInput: MediaDeviceInfo | null,
|
||||
): Promise<void> {
|
||||
this.connectionState = ConnectionState.Lobby;
|
||||
// Ensure that the messaging doesn't get stopped while we're waiting for responses
|
||||
const dontStopMessaging = new Promise<void>((resolve, reject) => {
|
||||
const messagingStore = WidgetMessagingStore.instance;
|
||||
@@ -569,9 +582,9 @@ export class JitsiCall extends Call {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
private onRoomState = (): void => this.updateParticipants();
|
||||
private readonly onRoomState = (): void => this.updateParticipants();
|
||||
|
||||
private onConnectionState = async (state: ConnectionState, prevState: ConnectionState): Promise<void> => {
|
||||
private readonly onConnectionState = async (state: ConnectionState, prevState: ConnectionState): Promise<void> => {
|
||||
if (state === ConnectionState.Connected && !isConnected(prevState)) {
|
||||
this.updateParticipants(); // Local echo
|
||||
|
||||
@@ -597,18 +610,18 @@ export class JitsiCall extends Call {
|
||||
}
|
||||
};
|
||||
|
||||
private onDock = async (): Promise<void> => {
|
||||
private readonly onDock = async (): Promise<void> => {
|
||||
// The widget is no longer a PiP, so let's restore the default layout
|
||||
await this.messaging!.transport.send(ElementWidgetActions.TileLayout, {});
|
||||
};
|
||||
|
||||
private onUndock = async (): Promise<void> => {
|
||||
private readonly onUndock = async (): Promise<void> => {
|
||||
// The widget has become a PiP, so let's switch Jitsi to spotlight mode
|
||||
// to only show the active speaker and economize on space
|
||||
await this.messaging!.transport.send(ElementWidgetActions.SpotlightLayout, {});
|
||||
};
|
||||
|
||||
private onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
private readonly onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
// If we're already in the middle of a client-initiated disconnection,
|
||||
// ignore the event
|
||||
if (this.connectionState === ConnectionState.Disconnecting) return;
|
||||
@@ -617,14 +630,15 @@ export class JitsiCall extends Call {
|
||||
|
||||
// In case this hangup is caused by Jitsi Meet crashing at startup,
|
||||
// wait for the connection event in order to avoid racing
|
||||
if (this.connectionState === ConnectionState.Connecting) {
|
||||
if (this.connectionState === ConnectionState.Disconnected) {
|
||||
await waitForEvent(this, CallEvent.ConnectionState);
|
||||
}
|
||||
|
||||
await this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.setDisconnected();
|
||||
this.close();
|
||||
// In video rooms we immediately want to restart the call after hangup
|
||||
// The lobby will be shown again and it connects to all signals from EC and Jitsi.
|
||||
// The lobby will be shown again and it connects to all signals from Jitsi.
|
||||
if (isVideoRoom(this.room)) {
|
||||
this.start();
|
||||
}
|
||||
@@ -653,6 +667,14 @@ export class ElementCall extends Call {
|
||||
this.emit(CallEvent.Layout, value);
|
||||
}
|
||||
|
||||
public get presented(): boolean {
|
||||
return super.presented;
|
||||
}
|
||||
public set presented(value: boolean) {
|
||||
super.presented = value;
|
||||
this.checkDestroy();
|
||||
}
|
||||
|
||||
private static generateWidgetUrl(client: MatrixClient, roomId: string): URL {
|
||||
const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE);
|
||||
// The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget.
|
||||
@@ -740,7 +762,7 @@ export class ElementCall extends Call {
|
||||
// To use Element Call without touching room state, we create a virtual
|
||||
// widget (one that doesn't have a corresponding state event)
|
||||
const url = ElementCall.generateWidgetUrl(client, roomId);
|
||||
return WidgetStore.instance.addVirtualWidget(
|
||||
const createdWidget = WidgetStore.instance.addVirtualWidget(
|
||||
{
|
||||
id: secureRandomString(24), // So that it's globally unique
|
||||
creatorUserId: client.getUserId()!,
|
||||
@@ -761,6 +783,8 @@ export class ElementCall extends Call {
|
||||
},
|
||||
roomId,
|
||||
);
|
||||
WidgetStore.instance.emit(UPDATE_EVENT, null);
|
||||
return createdWidget;
|
||||
}
|
||||
|
||||
private static getWidgetData(
|
||||
@@ -794,7 +818,7 @@ export class ElementCall extends Call {
|
||||
super(widget, client);
|
||||
|
||||
this.session.on(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
|
||||
this.client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
|
||||
this.client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, this.checkDestroy);
|
||||
SettingsStore.watchSetting(
|
||||
"feature_disable_call_per_sender_encryption",
|
||||
null,
|
||||
@@ -827,9 +851,8 @@ export class ElementCall extends Call {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async create(room: Room, skipLobby = false): Promise<void> {
|
||||
public static create(room: Room, skipLobby = false): void {
|
||||
ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, false, isVideoRoom(room));
|
||||
WidgetStore.instance.emit(UPDATE_EVENT, null);
|
||||
}
|
||||
|
||||
protected async sendCallNotify(): Promise<void> {
|
||||
@@ -875,17 +898,9 @@ export class ElementCall extends Call {
|
||||
this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, async (ev) => {
|
||||
ev.preventDefault();
|
||||
await this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
});
|
||||
this.messaging!.once(`action:${ElementWidgetActions.Close}`, this.onClose);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
|
||||
|
||||
if (!this.widget.data?.skipLobby) {
|
||||
// If we do not skip the lobby we need to wait until the widget has
|
||||
// connected to matrixRTC. This is either observed through the session state
|
||||
// or the MatrixRTCSessionManager session started event.
|
||||
this.connectionState = ConnectionState.Lobby;
|
||||
}
|
||||
// TODO: if the widget informs us when the join button is clicked (widget action), so we can
|
||||
// - set state to connecting
|
||||
// - send call notify
|
||||
@@ -927,15 +942,16 @@ export class ElementCall extends Call {
|
||||
public setDisconnected(): void {
|
||||
this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
|
||||
super.setDisconnected();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
ActiveWidgetStore.instance.destroyPersistentWidget(this.widget.id, this.widget.roomId);
|
||||
WidgetStore.instance.removeVirtualWidget(this.widget.id, this.widget.roomId);
|
||||
this.messaging?.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
this.session.off(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
|
||||
this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
|
||||
this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.checkDestroy);
|
||||
|
||||
SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher);
|
||||
clearTimeout(this.terminationTimer);
|
||||
@@ -944,11 +960,10 @@ export class ElementCall extends Call {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
private onRTCSessionEnded = (roomId: string, session: MatrixRTCSession): void => {
|
||||
// Don't destroy the call on hangup for video call rooms.
|
||||
if (roomId === this.roomId && !this.room.isCallRoom()) {
|
||||
this.destroy();
|
||||
}
|
||||
private checkDestroy = (): void => {
|
||||
// A call ceases to exist as soon as all participants leave and also the
|
||||
// user isn't looking at it (for example, waiting in an empty lobby)
|
||||
if (this.session.memberships.length === 0 && !this.presented && !this.room.isCallRoom()) this.destroy();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -960,7 +975,7 @@ export class ElementCall extends Call {
|
||||
await this.messaging!.transport.send(action, {});
|
||||
}
|
||||
|
||||
private onMembershipChanged = (): void => this.updateParticipants();
|
||||
private readonly onMembershipChanged = (): void => this.updateParticipants();
|
||||
|
||||
private updateParticipants(): void {
|
||||
const participants = new Map<RoomMember, Set<string>>();
|
||||
@@ -980,27 +995,40 @@ export class ElementCall extends Call {
|
||||
this.participants = participants;
|
||||
}
|
||||
|
||||
private onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
private readonly onDeviceMute = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
ev.preventDefault();
|
||||
await this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
};
|
||||
|
||||
private readonly onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.setDisconnected();
|
||||
};
|
||||
|
||||
private readonly onClose = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
// In video rooms we immediately want to reconnect after hangup
|
||||
// This starts the lobby again and connects to all signals from EC.
|
||||
if (isVideoRoom(this.room)) {
|
||||
this.start();
|
||||
} else {
|
||||
// User is done with the call; tell the UI to close it
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
private onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
private readonly onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
this.layout = Layout.Tile;
|
||||
await this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
};
|
||||
|
||||
private onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
private readonly onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
this.layout = Layout.Spotlight;
|
||||
await this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
};
|
||||
|
||||
public clean(): Promise<void> {
|
||||
|
||||
@@ -213,7 +213,6 @@ export async function initSentry(sentryConfig: IConfigOptions["sentry"]): Promis
|
||||
release: process.env.VERSION,
|
||||
environment: sentryConfig.environment,
|
||||
defaultIntegrations: false,
|
||||
autoSessionTracking: false,
|
||||
integrations,
|
||||
// Set to 1.0 which is reasonable if we're only submitting Rageshakes; will need to be set < 1.0
|
||||
// if we collect more frequently.
|
||||
|
||||
@@ -50,6 +50,8 @@ import { type CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJ
|
||||
import { type SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload";
|
||||
import { ModuleRunner } from "../modules/ModuleRunner";
|
||||
import { setMarkedUnreadState } from "../utils/notifications";
|
||||
import { ConnectionState, ElementCall } from "../models/Call";
|
||||
import { isVideoRoom } from "../utils/video-rooms";
|
||||
|
||||
const NUM_JOIN_RETRY = 5;
|
||||
|
||||
@@ -353,6 +355,23 @@ export class RoomViewStore extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
if (room && (payload.view_call || isVideoRoom(room))) {
|
||||
let call = CallStore.instance.getCall(payload.room_id);
|
||||
// Start a call if not already there
|
||||
if (call === null) {
|
||||
ElementCall.create(room, false);
|
||||
call = CallStore.instance.getCall(payload.room_id)!;
|
||||
}
|
||||
call.presented = true;
|
||||
// Immediately start the call. This will connect to all required widget events
|
||||
// and allow the widget to show the lobby.
|
||||
if (call.connectionState === ConnectionState.Disconnected) call.start();
|
||||
}
|
||||
// If we switch to a different room from the call, we are no longer presenting it
|
||||
const prevRoomCall = this.state.roomId ? CallStore.instance.getCall(this.state.roomId) : null;
|
||||
if (prevRoomCall !== null && (!payload.view_call || payload.room_id !== this.state.roomId))
|
||||
prevRoomCall.presented = false;
|
||||
|
||||
if (SettingsStore.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) {
|
||||
if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) {
|
||||
// unsubscribe from this room, but don't await it as we don't care when this gets done.
|
||||
|
||||
@@ -12,6 +12,7 @@ export enum ElementWidgetActions {
|
||||
// All of these actions are currently specific to Jitsi and Element Call
|
||||
JoinCall = "io.element.join",
|
||||
HangupCall = "im.vector.hangup",
|
||||
Close = "io.element.close",
|
||||
CallParticipants = "io.element.participants",
|
||||
StartLiveStream = "im.vector.start_live_stream",
|
||||
|
||||
|
||||
@@ -72,8 +72,11 @@ export class WidgetMessagingStore extends AsyncStoreWithClient<EmptyObject> {
|
||||
* @param {string} widgetUid The widget UID.
|
||||
*/
|
||||
public stopMessagingByUid(widgetUid: string): void {
|
||||
this.widgetMap.remove(widgetUid)?.stop();
|
||||
this.emit(WidgetMessagingStoreEvent.StopMessaging, widgetUid);
|
||||
const messaging = this.widgetMap.remove(widgetUid);
|
||||
if (messaging !== undefined) {
|
||||
messaging.stop();
|
||||
this.emit(WidgetMessagingStoreEvent.StopMessaging, widgetUid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
background: #f9fafb;
|
||||
max-width: 680px;
|
||||
margin: auto;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
background: #f9fafb;
|
||||
max-width: 680px;
|
||||
margin: auto;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
background: #f9fafb;
|
||||
max-width: 680px;
|
||||
margin: auto;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
|
||||
@@ -109,5 +109,5 @@ export class MockedCall extends Call {
|
||||
export const useMockedCalls = () => {
|
||||
Call.get = (room) => MockedCall.get(room);
|
||||
JitsiCall.create = async (room) => MockedCall.create(room, "1");
|
||||
ElementCall.create = async (room) => MockedCall.create(room, "1");
|
||||
ElementCall.create = (room) => MockedCall.create(room, "1");
|
||||
};
|
||||
|
||||