From 13fbd096b0ef42f412bdb20c429c5c0e1c382423 Mon Sep 17 00:00:00 2001 From: kegsay Date: Wed, 19 Oct 2022 14:14:14 +0100 Subject: [PATCH 01/18] Stores refactor: convert TypingStore; rename TestStores to TestSdkContext (#9454) --- src/Lifecycle.ts | 6 +++--- src/components/views/rooms/BasicMessageComposer.tsx | 4 ++-- src/contexts/SDKContext.ts | 9 +++++++++ src/stores/TypingStore.ts | 13 +++---------- test/{TestStores.ts => TestSdkContext.ts} | 4 ++-- test/stores/RoomViewStore-test.ts | 4 ++-- test/stores/TypingStore-test.ts | 8 +++++--- 7 files changed, 26 insertions(+), 22 deletions(-) rename test/{TestStores.ts => TestSdkContext.ts} (91%) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 6c9c955818..9351e91ae4 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -39,7 +39,6 @@ import PlatformPeg from "./PlatformPeg"; import { sendLoginRequest } from "./Login"; import * as StorageManager from './utils/StorageManager'; import SettingsStore from "./settings/SettingsStore"; -import TypingStore from "./stores/TypingStore"; import ToastStore from "./stores/ToastStore"; import { IntegrationManagers } from "./integrations/IntegrationManagers"; import { Mjolnir } from "./mjolnir/Mjolnir"; @@ -62,6 +61,7 @@ import { DialogOpener } from "./utils/DialogOpener"; import { Action } from "./dispatcher/actions"; import AbstractLocalStorageSettingsHandler from "./settings/handlers/AbstractLocalStorageSettingsHandler"; import { OverwriteLoginPayload } from "./dispatcher/payloads/OverwriteLoginPayload"; +import { SdkContextClass } from './contexts/SDKContext'; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; @@ -797,7 +797,7 @@ async function startMatrixClient(startSyncing = true): Promise { dis.dispatch({ action: 'will_start_client' }, true); // reset things first just in case - TypingStore.sharedInstance().reset(); + SdkContextClass.instance.typingStore.reset(); ToastStore.sharedInstance().reset(); DialogOpener.instance.prepare(); @@ -927,7 +927,7 @@ export function stopMatrixClient(unsetClient = true): void { Notifier.stop(); LegacyCallHandler.instance.stop(); UserActivity.sharedInstance().stop(); - TypingStore.sharedInstance().reset(); + SdkContextClass.instance.typingStore.reset(); Presence.stop(); ActiveWidgetStore.instance.stop(); IntegrationManagers.sharedInstance().stopWatching(); diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index d74c7b5148..4c2201a628 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -31,7 +31,6 @@ import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete'; import { getAutoCompleteCreator, Part, Type } from '../../../editor/parts'; import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize'; import { renderModel } from '../../../editor/render'; -import TypingStore from "../../../stores/TypingStore"; import SettingsStore from "../../../settings/SettingsStore"; import { IS_MAC, Key } from "../../../Keyboard"; import { EMOTICON_TO_EMOJI } from "../../../emoji"; @@ -47,6 +46,7 @@ import { getKeyBindingsManager } from '../../../KeyBindingsManager'; import { ALTERNATE_KEY_NAME, KeyBindingAction } from '../../../accessibility/KeyboardShortcuts'; import { _t } from "../../../languageHandler"; import { linkify } from '../../../linkify-matrix'; +import { SdkContextClass } from '../../../contexts/SDKContext'; // matches emoticons which follow the start of a line or whitespace const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s|:^$'); @@ -246,7 +246,7 @@ export default class BasicMessageEditor extends React.Component isTyping = false; } } - TypingStore.sharedInstance().setSelfTyping( + SdkContextClass.instance.typingStore.setSelfTyping( this.props.room.roomId, this.props.threadId, isTyping, diff --git a/src/contexts/SDKContext.ts b/src/contexts/SDKContext.ts index 61905dca92..8e6222512b 100644 --- a/src/contexts/SDKContext.ts +++ b/src/contexts/SDKContext.ts @@ -25,6 +25,7 @@ import { RoomNotificationStateStore } from "../stores/notifications/RoomNotifica import RightPanelStore from "../stores/right-panel/RightPanelStore"; import { RoomViewStore } from "../stores/RoomViewStore"; import SpaceStore, { SpaceStoreClass } from "../stores/spaces/SpaceStore"; +import TypingStore from "../stores/TypingStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; import WidgetStore from "../stores/WidgetStore"; @@ -59,6 +60,7 @@ export class SdkContextClass { protected _SlidingSyncManager?: SlidingSyncManager; protected _SpaceStore?: SpaceStoreClass; protected _LegacyCallHandler?: LegacyCallHandler; + protected _TypingStore?: TypingStore; /** * Automatically construct stores which need to be created eagerly so they can register with @@ -124,4 +126,11 @@ export class SdkContextClass { } return this._SpaceStore; } + public get typingStore(): TypingStore { + if (!this._TypingStore) { + this._TypingStore = new TypingStore(this); + window.mxTypingStore = this._TypingStore; + } + return this._TypingStore; + } } diff --git a/src/stores/TypingStore.ts b/src/stores/TypingStore.ts index d642f3fea7..be17da6e4e 100644 --- a/src/stores/TypingStore.ts +++ b/src/stores/TypingStore.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClientPeg } from "../MatrixClientPeg"; +import { SdkContextClass } from "../contexts/SDKContext"; import SettingsStore from "../settings/SettingsStore"; import { isLocalRoom } from "../utils/localRoom/isLocalRoom"; import Timer from "../utils/Timer"; @@ -34,17 +34,10 @@ export default class TypingStore { }; }; - constructor() { + constructor(private readonly context: SdkContextClass) { this.reset(); } - public static sharedInstance(): TypingStore { - if (window.mxTypingStore === undefined) { - window.mxTypingStore = new TypingStore(); - } - return window.mxTypingStore; - } - /** * Clears all cached typing states. Intended to be called when the * MatrixClientPeg client changes. @@ -108,6 +101,6 @@ export default class TypingStore { } else currentTyping.userTimer.restart(); } - MatrixClientPeg.get().sendTyping(roomId, isTyping, TYPING_SERVER_TIMEOUT); + this.context.client?.sendTyping(roomId, isTyping, TYPING_SERVER_TIMEOUT); } } diff --git a/test/TestStores.ts b/test/TestSdkContext.ts similarity index 91% rename from test/TestStores.ts rename to test/TestSdkContext.ts index dbaa51f504..137b71f9a3 100644 --- a/test/TestStores.ts +++ b/test/TestSdkContext.ts @@ -25,10 +25,10 @@ import { WidgetLayoutStore } from "../src/stores/widgets/WidgetLayoutStore"; import WidgetStore from "../src/stores/WidgetStore"; /** - * A class which provides the same API as Stores but adds additional unsafe setters which can + * A class which provides the same API as SdkContextClass but adds additional unsafe setters which can * replace individual stores. This is useful for tests which need to mock out stores. */ -export class TestStores extends SdkContextClass { +export class TestSdkContext extends SdkContextClass { public _RightPanelStore?: RightPanelStore; public _RoomNotificationStateStore?: RoomNotificationStateStore; public _RoomViewStore?: RoomViewStore; diff --git a/test/stores/RoomViewStore-test.ts b/test/stores/RoomViewStore-test.ts index f6f6bf2cc7..5f1bb98d3d 100644 --- a/test/stores/RoomViewStore-test.ts +++ b/test/stores/RoomViewStore-test.ts @@ -27,7 +27,7 @@ import { MatrixDispatcher } from '../../src/dispatcher/dispatcher'; import { UPDATE_EVENT } from '../../src/stores/AsyncStore'; import { ActiveRoomChangedPayload } from '../../src/dispatcher/payloads/ActiveRoomChangedPayload'; import { SpaceStoreClass } from '../../src/stores/spaces/SpaceStore'; -import { TestStores } from '../TestStores'; +import { TestSdkContext } from '../TestSdkContext'; // mock out the injected classes jest.mock('../../src/PosthogAnalytics'); @@ -77,7 +77,7 @@ describe('RoomViewStore', function() { // Make the RVS to test dis = new MatrixDispatcher(); slidingSyncManager = new MockSlidingSyncManager(); - const stores = new TestStores(); + const stores = new TestSdkContext(); stores._SlidingSyncManager = slidingSyncManager; stores._PosthogAnalytics = new MockPosthogAnalytics(); stores._SpaceStore = new MockSpaceStore(); diff --git a/test/stores/TypingStore-test.ts b/test/stores/TypingStore-test.ts index 98ddfca3c4..a5b4437f14 100644 --- a/test/stores/TypingStore-test.ts +++ b/test/stores/TypingStore-test.ts @@ -17,13 +17,14 @@ limitations under the License. import { mocked } from "jest-mock"; import { MatrixClient } from "matrix-js-sdk/src/matrix"; -import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import TypingStore from "../../src/stores/TypingStore"; import { LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom"; import SettingsStore from "../../src/settings/SettingsStore"; +import { TestSdkContext } from "../TestSdkContext"; jest.mock("../../src/settings/SettingsStore", () => ({ getValue: jest.fn(), + monitorSetting: jest.fn(), })); describe("TypingStore", () => { @@ -37,11 +38,12 @@ describe("TypingStore", () => { const localRoomId = LOCAL_ROOM_ID_PREFIX + "test"; beforeEach(() => { - typingStore = new TypingStore(); mockClient = { sendTyping: jest.fn(), } as unknown as MatrixClient; - MatrixClientPeg.get = () => mockClient; + const context = new TestSdkContext(); + context.client = mockClient; + typingStore = new TypingStore(context); mocked(SettingsStore.getValue).mockImplementation((setting: string) => { return settings[setting]; }); From 8066b9ffbe4b13c92bb2cb67ca559843a0052696 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Wed, 19 Oct 2022 16:22:07 +0200 Subject: [PATCH 02/18] Prevent starting another voice broadcast (#9457) --- .../utils/startNewVoiceBroadcastRecording.tsx | 5 ++++ ...artNewVoiceBroadcastRecording-test.ts.snap | 25 ++++++++++++++++++- .../startNewVoiceBroadcastRecording-test.ts | 21 +++++++++++++++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.tsx b/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.tsx index cff195c668..1084d6d2f3 100644 --- a/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.tsx +++ b/src/voice-broadcast/utils/startNewVoiceBroadcastRecording.tsx @@ -113,6 +113,11 @@ export const startNewVoiceBroadcastRecording = async ( client: MatrixClient, recordingsStore: VoiceBroadcastRecordingsStore, ): Promise => { + if (recordingsStore.getCurrent()) { + showAlreadyRecordingDialog(); + return null; + } + const currentUserId = client.getUserId(); if (!room.currentState.maySendStateEvent(VoiceBroadcastInfoEventType, currentUserId)) { diff --git a/test/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap b/test/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap index c38673e3b6..7ea5b24355 100644 --- a/test/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap +++ b/test/voice-broadcast/utils/__snapshots__/startNewVoiceBroadcastRecording-test.ts.snap @@ -23,7 +23,30 @@ exports[`startNewVoiceBroadcastRecording when the current user is allowed to sen } `; -exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user should show an info dialog 1`] = ` +exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user in the room should show an info dialog 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + [Function], + Object { + "description":

+ You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. +

, + "hasCloseButton": true, + "title": "Can't start a new voice broadcast", + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], +} +`; + +exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there is already a current voice broadcast should show an info dialog 1`] = ` [MockFunction] { "calls": Array [ Array [ diff --git a/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts b/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts index a320bca2eb..0fc0d14cb2 100644 --- a/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts @@ -67,6 +67,7 @@ describe("startNewVoiceBroadcastRecording", () => { recordingsStore = { setCurrent: jest.fn(), + getCurrent: jest.fn(), } as unknown as VoiceBroadcastRecordingsStore; infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, client.getUserId()); @@ -132,7 +133,25 @@ describe("startNewVoiceBroadcastRecording", () => { }); }); - describe("when there already is a live broadcast of the current user", () => { + describe("when there is already a current voice broadcast", () => { + beforeEach(async () => { + mocked(recordingsStore.getCurrent).mockReturnValue( + new VoiceBroadcastRecording(infoEvent, client), + ); + + result = await startNewVoiceBroadcastRecording(room, client, recordingsStore); + }); + + it("should not start a voice broadcast", () => { + expect(result).toBeNull(); + }); + + it("should show an info dialog", () => { + expect(Modal.createDialog).toMatchSnapshot(); + }); + }); + + describe("when there already is a live broadcast of the current user in the room", () => { beforeEach(async () => { room.currentState.setStateEvents([ mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Running, client.getUserId()), From d5a4718d461b7c60b571914217d8a9425da3af1e Mon Sep 17 00:00:00 2001 From: Kerry Date: Wed, 19 Oct 2022 17:11:42 +0200 Subject: [PATCH 03/18] Test display of qr code login section (#9456) * Support for login + E2EE set up with QR * Whitespace * Padding * Refactor of fetch * Whitespace * CSS whitespace * Add link to MSC3906 * Handle incorrect typing in MatrixClientPeg.get() * Use unstable class name * fix: use unstable class name * Use default fetch client instead * Update to revised function name * Refactor device manager panel and make it work with new sessions manager * Lint fix * Add missing interstitials and update wording * Linting * i18n * Lint * Use sensible sdk config name for fallback server * Improve error handling for QR code generation * Refactor feature availability logic * Hide device manager panel if no options available * Put sign in with QR behind lab setting * Reduce scope of PR to just showing code on existing device * i18n updates * Handle null features * Testing for LoginWithQRSection * Refactor to handle UIA * Imports * Reduce diff complexity * Remove unnecessary change * Remove unused styles * Support UIA * Tidy up * i18n * Remove additional unused parts of flow * Add extra instruction when showing QR code * Add getVersions to server mocks * Use proper colours for theme support * Test cases * Lint * Remove obsolete snapshot * Don't override error if already set * Remove unused var * Update src/components/views/settings/devices/LoginWithQRSection.tsx Co-authored-by: Travis Ralston * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston * Update src/components/views/auth/LoginWithQR.tsx Co-authored-by: Travis Ralston * Update res/css/views/auth/_LoginWithQR.pcss Co-authored-by: Kerry * Use spacing variables * Remove debug * Style + docs * preventDefault * Names of tests * Fixes for js-sdk refactor * Update snapshots to match test names * Refactor labs config to make deployment simpler * i18n * Unused imports * Typo * Stateless component * Whitespace * Use context not MatrixClientPeg * Add missing context * Type updates to match js-sdk * Wrap click handlers in useCallback * Update src/components/views/settings/DevicesPanel.tsx Co-authored-by: Travis Ralston * Wait for DOM update instead of timeout * Add missing snapshot update from last commit * Remove void keyword in favour of then() clauses * test main paths in LoginWithQR * test coverage for display of qr code section * remove unused test props Co-authored-by: Hugh Nimmo-Smith Co-authored-by: Hugh Nimmo-Smith Co-authored-by: Travis Ralston --- src/components/views/auth/LoginWithQR.tsx | 2 +- .../__snapshots__/LoginWithQR-test.tsx.snap | 7 +++ .../user/SecurityUserSettingsTab-test.tsx | 39 ++++++++++++++- .../tabs/user/SessionManagerTab-test.tsx | 48 +++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/components/views/auth/LoginWithQR.tsx b/src/components/views/auth/LoginWithQR.tsx index f95e618cc5..3d3f76be95 100644 --- a/src/components/views/auth/LoginWithQR.tsx +++ b/src/components/views/auth/LoginWithQR.tsx @@ -370,7 +370,7 @@ export default class LoginWithQR extends React.Component { } return ( -
+
{ backButton ? approves login and waits for new device 1`] = `
displays confirmation digits after connected to rendezv
displays error when approving login fails 1`] = `
displays qr code after it is created 1`] = `
displays unknown error if connection to rendezvous fail
no content in case of no support 1`] = `
renders spinner while generating code 1`] = `
', () => { @@ -42,6 +43,12 @@ describe('', () => { ...mockClientMethodsCrypto(), getRooms: jest.fn().mockReturnValue([]), getIgnoredUsers: jest.fn(), + getVersions: jest.fn().mockResolvedValue({ + unstable_features: { + 'org.matrix.msc3882': true, + 'org.matrix.msc3886': true, + }, + }), }); const getComponent = () => @@ -70,4 +77,34 @@ describe('', () => { expect(queryByTestId('devices-section')).toBeFalsy(); }); + + it('does not render qr code login section when disabled', () => { + settingsValueSpy.mockReturnValue(false); + const { queryByText } = render(getComponent()); + + expect(settingsValueSpy).toHaveBeenCalledWith('feature_qr_signin_reciprocate_show'); + + expect(queryByText('Sign in with QR code')).toBeFalsy(); + }); + + it('renders qr code login section when enabled', async () => { + settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show'); + const { getByText } = render(getComponent()); + + // wait for versions call to settle + await flushPromises(); + + expect(getByText('Sign in with QR code')).toBeTruthy(); + }); + + it('enters qr code login section when show QR code button clicked', async () => { + settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show'); + const { getByText, getByTestId } = render(getComponent()); + // wait for versions call to settle + await flushPromises(); + + fireEvent.click(getByText('Show QR code')); + + expect(getByTestId("login-with-qr")).toBeTruthy(); + }); }); diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 7826b3cc80..e9dd352903 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -34,6 +34,7 @@ import { import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab'; import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext'; import { + flushPromises, flushPromisesWithFakeTimers, getMockClientWithEventEmitter, mkPusher, @@ -47,6 +48,7 @@ import { ExtendedDevice, } from '../../../../../../src/components/views/settings/devices/types'; import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter'; +import SettingsStore from '../../../../../../src/settings/SettingsStore'; mockPlatformPeg(); @@ -1142,4 +1144,50 @@ describe('', () => { expect(checkbox.getAttribute('aria-checked')).toEqual("false"); }); + + describe('QR code login', () => { + const settingsValueSpy = jest.spyOn(SettingsStore, 'getValue'); + + beforeEach(() => { + settingsValueSpy.mockClear().mockReturnValue(false); + // enable server support for qr login + mockClient.getVersions.mockResolvedValue({ + versions: [], + unstable_features: { + 'org.matrix.msc3882': true, + 'org.matrix.msc3886': true, + }, + }); + }); + + it('does not render qr code login section when disabled', () => { + settingsValueSpy.mockReturnValue(false); + const { queryByText } = render(getComponent()); + + expect(settingsValueSpy).toHaveBeenCalledWith('feature_qr_signin_reciprocate_show'); + + expect(queryByText('Sign in with QR code')).toBeFalsy(); + }); + + it('renders qr code login section when enabled', async () => { + settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show'); + const { getByText } = render(getComponent()); + + // wait for versions call to settle + await flushPromises(); + + expect(getByText('Sign in with QR code')).toBeTruthy(); + }); + + it('enters qr code login section when show QR code button clicked', async () => { + settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show'); + const { getByText, getByTestId } = render(getComponent()); + // wait for versions call to settle + await flushPromises(); + + fireEvent.click(getByText('Show QR code')); + + expect(getByTestId("login-with-qr")).toBeTruthy(); + }); + }); }); From 07a1e9a00941a53b528b119680c823d99ae19e47 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Wed, 19 Oct 2022 18:02:48 +0200 Subject: [PATCH 04/18] Stop voice broadcast recording on redaction (#9455) --- .../hooks/useVoiceBroadcastRecording.tsx | 2 - .../models/VoiceBroadcastRecording.ts | 12 +++- .../stores/VoiceBroadcastRecordingsStore.ts | 16 ++++- .../models/VoiceBroadcastRecording-test.ts | 14 +++++ .../VoiceBroadcastRecordingsStore-test.ts | 62 +++++++++++-------- 5 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx b/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx index c0db561746..341283c2ad 100644 --- a/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx +++ b/src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx @@ -20,7 +20,6 @@ import { VoiceBroadcastInfoState, VoiceBroadcastRecording, VoiceBroadcastRecordingEvent, - VoiceBroadcastRecordingsStore, } from ".."; import QuestionDialog from "../../components/views/dialogs/QuestionDialog"; import { useTypedEventEmitter } from "../../hooks/useEventEmitter"; @@ -54,7 +53,6 @@ export const useVoiceBroadcastRecording = (recording: VoiceBroadcastRecording) = if (confirmed) { recording.stop(); - VoiceBroadcastRecordingsStore.instance().clearCurrent(); } }; diff --git a/src/voice-broadcast/models/VoiceBroadcastRecording.ts b/src/voice-broadcast/models/VoiceBroadcastRecording.ts index 96b62a670f..f7faa0876e 100644 --- a/src/voice-broadcast/models/VoiceBroadcastRecording.ts +++ b/src/voice-broadcast/models/VoiceBroadcastRecording.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; +import { MatrixClient, MatrixEvent, MatrixEventEvent, RelationType } from "matrix-js-sdk/src/matrix"; import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter"; import { @@ -67,6 +67,7 @@ export class VoiceBroadcastRecording }) ? VoiceBroadcastInfoState.Started : VoiceBroadcastInfoState.Stopped; // TODO Michael W: add listening for updates + this.infoEvent.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); this.dispatcherRef = dis.register(this.onAction); } @@ -99,10 +100,19 @@ export class VoiceBroadcastRecording this.recorder.stop(); } + this.infoEvent.off(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction); this.removeAllListeners(); dis.unregister(this.dispatcherRef); } + private onBeforeRedaction = () => { + if (this.getState() !== VoiceBroadcastInfoState.Stopped) { + this.setState(VoiceBroadcastInfoState.Stopped); + // destroy cleans up everything + this.destroy(); + } + }; + private onAction = (payload: ActionPayload) => { if (payload.action !== "call_state") return; diff --git a/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts b/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts index cc12b474e8..b5c78a1b0e 100644 --- a/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts +++ b/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts @@ -17,7 +17,7 @@ limitations under the License. import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter"; -import { VoiceBroadcastRecording } from ".."; +import { VoiceBroadcastInfoState, VoiceBroadcastRecording, VoiceBroadcastRecordingEvent } from ".."; export enum VoiceBroadcastRecordingsStoreEvent { CurrentChanged = "current_changed", @@ -41,7 +41,12 @@ export class VoiceBroadcastRecordingsStore extends TypedEventEmitter { + if (state === VoiceBroadcastInfoState.Stopped) { + this.clearCurrent(); + } + }; + private static readonly cachedInstance = new VoiceBroadcastRecordingsStore(); /** diff --git a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts index 25a325aba7..049c03c5a4 100644 --- a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts @@ -20,6 +20,7 @@ import { EventType, MatrixClient, MatrixEvent, + MatrixEventEvent, MsgType, RelationType, Room, @@ -81,6 +82,7 @@ describe("VoiceBroadcastRecording", () => { const setUpVoiceBroadcastRecording = () => { voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client); voiceBroadcastRecording.on(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged); + jest.spyOn(voiceBroadcastRecording, "destroy"); jest.spyOn(voiceBroadcastRecording, "removeAllListeners"); }; @@ -214,6 +216,18 @@ describe("VoiceBroadcastRecording", () => { expect(voiceBroadcastRecorder.start).toHaveBeenCalled(); }); + describe("and the info event is redacted", () => { + beforeEach(() => { + infoEvent.emit(MatrixEventEvent.BeforeRedaction, null, null); + }); + + itShouldBeInState(VoiceBroadcastInfoState.Stopped); + + it("should destroy the recording", () => { + expect(voiceBroadcastRecording.destroy).toHaveBeenCalled(); + }); + }); + describe("and receiving a call action", () => { beforeEach(() => { dis.dispatch({ diff --git a/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts b/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts index 56b90b73ae..3edb74592e 100644 --- a/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts +++ b/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts @@ -18,28 +18,22 @@ import { mocked } from "jest-mock"; import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { - VoiceBroadcastInfoEventType, VoiceBroadcastRecordingsStore, VoiceBroadcastRecordingsStoreEvent, VoiceBroadcastRecording, + VoiceBroadcastInfoState, } from "../../../src/voice-broadcast"; -import { mkEvent, mkStubRoom, stubClient } from "../../test-utils"; - -jest.mock("../../../src/voice-broadcast/models/VoiceBroadcastRecording.ts", () => ({ - VoiceBroadcastRecording: jest.fn().mockImplementation( - ( - infoEvent: MatrixEvent, - client: MatrixClient, - ) => ({ infoEvent, client }), - ), -})); +import { mkStubRoom, stubClient } from "../../test-utils"; +import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils"; describe("VoiceBroadcastRecordingsStore", () => { const roomId = "!room:example.com"; let client: MatrixClient; let room: Room; let infoEvent: MatrixEvent; + let otherInfoEvent: MatrixEvent; let recording: VoiceBroadcastRecording; + let otherRecording: VoiceBroadcastRecording; let recordings: VoiceBroadcastRecordingsStore; let onCurrentChanged: (recording: VoiceBroadcastRecording) => void; @@ -51,22 +45,17 @@ describe("VoiceBroadcastRecordingsStore", () => { return room; } }); - infoEvent = mkEvent({ - event: true, - type: VoiceBroadcastInfoEventType, - user: client.getUserId(), - room: roomId, - content: {}, - }); - recording = { - infoEvent, - } as unknown as VoiceBroadcastRecording; + infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, client.getUserId()); + otherInfoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, client.getUserId()); + recording = new VoiceBroadcastRecording(infoEvent, client); + otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client); recordings = new VoiceBroadcastRecordingsStore(); onCurrentChanged = jest.fn(); recordings.on(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged); }); afterEach(() => { + recording.destroy(); recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged); }); @@ -110,6 +99,32 @@ describe("VoiceBroadcastRecordingsStore", () => { it("should emit a current changed event", () => { expect(onCurrentChanged).toHaveBeenCalledWith(null); }); + + it("and calling it again should work", () => { + recordings.clearCurrent(); + expect(recordings.getCurrent()).toBeNull(); + }); + }); + + describe("and setting another recording and stopping the previous recording", () => { + beforeEach(() => { + recordings.setCurrent(otherRecording); + recording.stop(); + }); + + it("should keep the current recording", () => { + expect(recordings.getCurrent()).toBe(otherRecording); + }); + }); + + describe("and the recording stops", () => { + beforeEach(() => { + recording.stop(); + }); + + it("should clear the current recording", () => { + expect(recordings.getCurrent()).toBeNull(); + }); }); }); @@ -133,10 +148,7 @@ describe("VoiceBroadcastRecordingsStore", () => { }); it("should return the recording", () => { - expect(returnedRecording).toEqual({ - infoEvent, - client, - }); + expect(returnedRecording.infoEvent).toBe(infoEvent); }); }); }); From f9a7d9fb7b2ef57cb291f154fcc0764772606687 Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 19 Oct 2022 15:11:13 -0400 Subject: [PATCH 05/18] Align video call icon with banner text (#9460) --- res/css/views/rooms/_RoomCallBanner.pcss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_RoomCallBanner.pcss b/res/css/views/rooms/_RoomCallBanner.pcss index 4b05b72d91..ec26807bb1 100644 --- a/res/css/views/rooms/_RoomCallBanner.pcss +++ b/res/css/views/rooms/_RoomCallBanner.pcss @@ -41,14 +41,14 @@ limitations under the License. &::before { display: inline-block; - vertical-align: text-top; + vertical-align: middle; content: ""; background-color: $secondary-content; mask-size: 16px; + mask-position-y: center; width: 16px; - height: 16px; - margin-right: 4px; - bottom: 2px; + height: 1.2em; /* to match line height */ + margin-right: 8px; mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } } From 7d0af1dca46b75aec23f7aa1a4d2c80746f5e56e Mon Sep 17 00:00:00 2001 From: Element Translate Bot Date: Wed, 19 Oct 2022 21:33:08 +0200 Subject: [PATCH 06/18] Translations update from Weblate (#9465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (German) Currently translated at 99.4% (3608 of 3629 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (German) Currently translated at 99.5% (3617 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (German) Currently translated at 99.7% (3624 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (German) Currently translated at 99.7% (3624 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (German) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (German) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (German) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (Italian) Currently translated at 99.2% (3607 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/it/ * Translated using Weblate (Estonian) Currently translated at 99.7% (3624 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (3606 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ * Translated using Weblate (Ukrainian) Currently translated at 99.3% (3611 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Czech) Currently translated at 99.3% (3608 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (German) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ * Translated using Weblate (Hungarian) Currently translated at 99.8% (3629 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/uk/ * Translated using Weblate (Czech) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ * Translated using Weblate (Slovak) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (3633 of 3633 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ Co-authored-by: Johannes Marbach Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: random Co-authored-by: Priit Jõerüüt Co-authored-by: Szimszon Co-authored-by: Ihor Hordiichuk Co-authored-by: waclaw66 Co-authored-by: Jozef Gaal --- src/i18n/strings/cs.json | 35 ++++++++++++++++++++++++++++++++--- src/i18n/strings/de_DE.json | 37 +++++++++++++++++++++++++++++++++---- src/i18n/strings/et.json | 34 +++++++++++++++++++++++++++++++++- src/i18n/strings/hu.json | 30 +++++++++++++++++++++++++++++- src/i18n/strings/it.json | 9 ++++++--- src/i18n/strings/sk.json | 34 +++++++++++++++++++++++++++++++++- src/i18n/strings/uk.json | 34 +++++++++++++++++++++++++++++++++- 7 files changed, 199 insertions(+), 14 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 9bd59626f6..eed5bb72c9 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -941,7 +941,7 @@ "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete si změnit heslo, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se toto varování zobrazuje i nadále, zkontrolujte svojí konfiguraci nebo kontaktujte správce serveru.", "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se přihlásit, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje i nadále, zkontrolujte svojí konfiguraci nebo kontaktujte správce serveru.", "Call failed due to misconfigured server": "Volání selhalo, protože je rozbitá konfigurace serveru", - "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Požádejte správce svého homeserveru (%(homeserverDomain)s) jestli by nemohl nakonfigurovat TURN server, aby volání fungovala spolehlivě.", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Požádejte správce svého domovského serveru (%(homeserverDomain)s) jestli by nemohl nakonfigurovat TURN server, aby volání fungovala spolehlivě.", "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Můžete také zkusit použít veřejný server na adrese turn.matrix.org, ale ten nebude tak spolehlivý a bude sdílet vaši IP adresu s tímto serverem. To můžete spravovat také v Nastavení.", "Try using turn.matrix.org": "Zkuste použít turn.matrix.org", "Messages": "Zprávy", @@ -1441,7 +1441,7 @@ "Manually Verify by Text": "Manuální textové ověření", "Interactively verify by Emoji": "Interaktivní ověření s emotikonami", "Support adding custom themes": "Umožnit přidání vlastního vzhledu", - "Manually verify all remote sessions": "Manuálně ověřit všechny relace", + "Manually verify all remote sessions": "Ručně ověřit všechny relace", "cached locally": "uložen lokálně", "not found locally": "nenalezen lolálně", "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individuálně ověřit každou uživatelovu relaci a označit jí za důvěryhodnou, bez důvěry v křížový podpis.", @@ -3635,5 +3635,34 @@ "Notifications silenced": "Oznámení ztlumena", "Yes, stop broadcast": "Ano, zastavit vysílání", "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Opravdu chcete ukončit živé vysílání? Tím se vysílání ukončí a v místnosti bude k dispozici celý záznam.", - "Stop live broadcasting?": "Ukončit živé vysílání?" + "Stop live broadcasting?": "Ukončit živé vysílání?", + "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Hlasové vysílání už nahrává někdo jiný. Počkejte, až jeho hlasové vysílání skončí, a spusťte nové.", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Nemáte potřebná oprávnění ke spuštění hlasového vysílání v této místnosti. Obraťte se na správce místnosti, aby vám zvýšil oprávnění.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Již nahráváte hlasové vysílání. Ukončete prosím aktuální hlasové vysílání a spusťte nové.", + "Can't start a new voice broadcast": "Nelze spustit nové hlasové vysílání", + "Completing set up of your new device": "Dokončování nastavení nového zařízení", + "Waiting for device to sign in": "Čekání na přihlášení zařízení", + "Connecting...": "Připojování...", + "Review and approve the sign in": "Zkontrolovat a schválit přihlášení", + "Select 'Scan QR code'": "Vyberte \"Naskenovat QR kód\"", + "Start at the sign in screen": "Začněte na přihlašovací obrazovce", + "Scan the QR code below with your device that's signed out.": "Níže uvedený QR kód naskenujte pomocí přihlašovaného zařízení.", + "By approving access for this device, it will have full access to your account.": "Schválením přístupu tohoto zařízení získá zařízení plný přístup k vašemu účtu.", + "Check that the code below matches with your other device:": "Zkontrolujte, zda se níže uvedený kód shoduje s vaším dalším zařízením:", + "Devices connected": "Zařízení byla propojena", + "The homeserver doesn't support signing in another device.": "Domovský server nepodporuje přihlášení pomocí jiného zařízení.", + "An unexpected error occurred.": "Došlo k neočekávané chybě.", + "The request was cancelled.": "Požadavek byl zrušen.", + "The other device isn't signed in.": "Druhé zařízení není přihlášeno.", + "The other device is already signed in.": "Druhé zařízení je již přihlášeno.", + "The request was declined on the other device.": "Požadavek byl na druhém zařízení odmítnut.", + "Linking with this device is not supported.": "Propojení s tímto zařízením není podporováno.", + "The scanned code is invalid.": "Naskenovaný kód je neplatný.", + "The linking wasn't completed in the required time.": "Propojení nebylo dokončeno v požadovaném čase.", + "Sign in new device": "Přihlásit nové zařízení", + "Show QR code": "Zobrazit QR kód", + "You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.": "Toto zařízení můžete použít k přihlášení nového zařízení pomocí QR kódu. QR kód zobrazený na tomto zařízení musíte naskenovat pomocí odhlášeného zařízení.", + "Sign in with QR code": "Přihlásit se pomocí QR kódu", + "Browser": "Prohlížeč", + "Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Povolit zobrazení QR kódu ve správci relací pro přihlášení do jiného zařízení (vyžaduje kompatibilní domovský server)" } diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 5b07a37cec..60586e4088 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -919,7 +919,7 @@ "Only continue if you trust the owner of the server.": "Fahre nur fort, wenn du den Server-Betreibenden vertraust.", "Trust": "Vertrauen", "Custom (%(level)s)": "Benutzerdefiniert (%(level)s)", - "Sends a message as plain text, without interpreting it as markdown": "Verschickt eine Nachricht in Rohtext, ohne sie als Markdown darzustellen", + "Sends a message as plain text, without interpreting it as markdown": "Sendet eine Nachricht als Klartext, ohne sie als Markdown darzustellen", "Use an identity server to invite by email. Manage in Settings.": "Verwende einen Identitäts-Server, um per E-Mail einladen zu können. Lege einen in den Einstellungen fest.", "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", "Try out new ways to ignore people (experimental)": "Verwende neue Möglichkeiten, Menschen zu blockieren", @@ -1166,7 +1166,7 @@ "%(creator)s created and configured the room.": "%(creator)s hat den Raum erstellt und konfiguriert.", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Bewahre eine Kopie an einem sicheren Ort, wie einem Passwort-Manager oder in einem Safe auf.", "Copy": "Kopieren", - "Sends a message as html, without interpreting it as markdown": "Verschickt eine Nachricht im HTML-Format, ohne sie als Markdown zu darzustellen", + "Sends a message as html, without interpreting it as markdown": "Sendet eine Nachricht als HTML, ohne sie als Markdown darzustellen", "Show rooms with unread notifications first": "Zeige Räume mit ungelesenen Benachrichtigungen zuerst an", "Show shortcuts to recently viewed rooms above the room list": "Kürzlich besuchte Räume anzeigen", "Use Single Sign On to continue": "Einmalanmeldung zum Fortfahren nutzen", @@ -3624,11 +3624,40 @@ "resume voice broadcast": "Sprachübertragung fortsetzen", "Italic": "Kursiv", "Underline": "Unterstrichen", - "Try out the rich text editor (plain text mode coming soon)": "Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus)", + "Try out the rich text editor (plain text mode coming soon)": "Probiere den Textverarbeitungs-Editor (bald auch mit Klartext-Modus)", "You have already joined this call from another device": "Du nimmst an diesem Anruf bereits mit einem anderen Gerät teil", "stop voice broadcast": "Sprachübertragung beenden", "Notifications silenced": "Benachrichtigungen stummgeschaltet", "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Willst du die Sprachübertragung wirklich beenden? Damit endet auch die Aufnahme.", "Yes, stop broadcast": "Ja, Sprachübertragung beenden", - "Stop live broadcasting?": "Sprachübertragung beenden?" + "Stop live broadcasting?": "Sprachübertragung beenden?", + "Sign in with QR code": "Mit QR-Code anmelden", + "Browser": "Browser", + "Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Erlaube es andere Geräte mittels QR-Code in der Sitzungsverwaltung anzumelden (kompatibler Heim-Server benötigt)", + "Completing set up of your new device": "Schließe Anmeldung deines neuen Gerätes ab", + "Waiting for device to sign in": "Warte auf Anmeldung des Gerätes", + "Connecting...": "Verbinde …", + "Review and approve the sign in": "Überprüfe und genehmige die Anmeldung", + "Select 'Scan QR code'": "Wähle „QR-Code einlesen“", + "Start at the sign in screen": "Beginne auf dem Anmeldebildschirm", + "Scan the QR code below with your device that's signed out.": "Lese den folgenden QR-Code mit deinem nicht angemeldeten Gerät ein.", + "By approving access for this device, it will have full access to your account.": "Indem du den Zugriff dieses Gerätes bestätigst, erhält es vollen Zugang zu deinem Account.", + "Check that the code below matches with your other device:": "Überprüfe, dass der unten angezeigte Code mit deinem anderen Gerät übereinstimmt:", + "Devices connected": "Geräte verbunden", + "The homeserver doesn't support signing in another device.": "Der Heim-Server unterstützt die Anmeldung eines anderen Gerätes nicht.", + "An unexpected error occurred.": "Ein unerwarteter Fehler ist aufgetreten.", + "The request was cancelled.": "Die Anfrage wurde abgebrochen.", + "The other device isn't signed in.": "Das andere Gerät ist nicht angemeldet.", + "The other device is already signed in.": "Das andere Gerät ist bereits angemeldet.", + "The request was declined on the other device.": "Die Anfrage wurde auf dem anderen Gerät abgelehnt.", + "Linking with this device is not supported.": "Die Verbindung mit diesem Gerät wird nicht unterstützt.", + "The scanned code is invalid.": "Der gescannte Code ist ungültig.", + "The linking wasn't completed in the required time.": "Die Verbindung konnte nicht in der erforderlichen Zeit hergestellt werden.", + "Sign in new device": "Neues Gerät anmelden", + "Show QR code": "QR-Code anzeigen", + "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Jemand anderes nimmt bereits eine Sprachübertragung auf. Warte auf das Ende der Übertragung, bevor du eine neue startest.", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Du hast nicht die nötigen Berechtigungen, um eine Sprachübertragung in diesem Raum zu starten. Kontaktiere einen Raumadministrator, um deine Berechtigungen anzupassen.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Du zeichnest bereits eine Sprachübertragung auf. Bitte beende die laufende Übertragung, um eine neue zu beginnen.", + "Can't start a new voice broadcast": "Sprachübertragung kann nicht gestartet werden", + "You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.": "Du kannst dieses Gerät verwenden, um ein neues Gerät per QR-Code anzumelden. Dazu musst du den auf diesem Gerät angezeigten QR-Code mit deinem nicht angemeldeten Gerät einlesen." } diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index f6a6e49b25..fabcc93019 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3631,5 +3631,37 @@ "New session manager": "Uus sessioonihaldur", "Use new session manager": "Kasuta uut sessioonihaldurit", "Try out the rich text editor (plain text mode coming soon)": "Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim)", - "Notifications silenced": "Teavitused on summutatud" + "Notifications silenced": "Teavitused on summutatud", + "Completing set up of your new device": "Lõpetame uue seadme seadistamise", + "Waiting for device to sign in": "Ootame, et teine seade logiks võrku", + "Connecting...": "Ühendamisel…", + "Review and approve the sign in": "Vaata üle ja kinnita sisselogimine Matrixi'i võrku", + "Select 'Scan QR code'": "Vali „Loe QR-koodi“", + "Start at the sign in screen": "Alusta sisselogimisvaatest", + "Scan the QR code below with your device that's signed out.": "Loe QR-koodi seadmega, kus sa oled Matrix'i võrgust välja loginud.", + "By approving access for this device, it will have full access to your account.": "Lubades ligipääsu sellele seadmele, annad talle ka täismahulise ligipääsu oma kasutajakontole.", + "Check that the code below matches with your other device:": "Kontrolli, et järgnev kood klapib teises seadmes kuvatava koodiga:", + "Devices connected": "Seadmed on ühendatud", + "The homeserver doesn't support signing in another device.": "Koduserver ei toeta muude seadmete võrku logimise võimalust.", + "An unexpected error occurred.": "Tekkis teadmata viga.", + "The request was cancelled.": "Päring katkestati.", + "The other device isn't signed in.": "Teine seade ei ole võrku loginud.", + "The other device is already signed in.": "Teine seade on juba võrku loginud.", + "The request was declined on the other device.": "Teine seade lükkas päringu tagasi.", + "Linking with this device is not supported.": "Sidumine selle seadmega ei ole toetatud.", + "The scanned code is invalid.": "Skaneeritud QR-kood on vigane.", + "The linking wasn't completed in the required time.": "Sidumine ei lõppenud etteantud aja jooksul.", + "Sign in new device": "Logi sisse uus seade", + "Show QR code": "Näita QR-koodi", + "Sign in with QR code": "Logi sisse QR-koodi abil", + "Browser": "Brauser", + "You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.": "Sa saad kasutada seda seadet mõne muu seadme logimiseks Matrix'i võrku QR-koodi alusel. Selleks skaneeri võrgust väljalogitud seadmega seda QR-koodi.", + "Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Teise seadme sisselogimiseks luba QR-koodi kuvamine sessioonihalduris (eeldab, et koduserver sellist võimalust toetab)", + "Yes, stop broadcast": "Jah, lõpeta", + "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Kas sa oled kindel, et soovid otseeetri lõpetada? Sellega ringhäälingukõne salvestamine lõppeb ja salvestis on kättesaadav kõigile jututoas.", + "Stop live broadcasting?": "Kas lõpetame otseeetri?", + "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Keegi juba salvestab ringhäälingukõnet. Uue ringhäälingukõne salvestamiseks palun oota, kuni see teine ringhäälingukõne on lõppenud.", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Sul pole piisavalt õigusi selles jututoas ringhäälingukõne algatamiseks. Õiguste lisamiseks palun võta ühendust jututoa haldajaga.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Sa juba salvestad ringhäälingukõnet. Uue alustamiseks palun lõpeta eelmine salvestus.", + "Can't start a new voice broadcast": "Uue ringhäälingukõne alustamine pole võimalik" } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index bba23baba9..de6d822e02 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3631,5 +3631,33 @@ "You do not have sufficient permissions to change this.": "Nincs megfelelő jogosultság a megváltoztatáshoz.", "%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s végpontok között titkosított de jelenleg csak kevés számú résztvevővel működik.", "Enable %(brand)s as an additional calling option in this room": "%(brand)s engedélyezése mint további opció hívásokhoz a szobában", - "Notifications silenced": "Értesítések elnémítva" + "Notifications silenced": "Értesítések elnémítva", + "Stop live broadcasting?": "Megszakítja az élő közvetítést?", + "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Valaki már elindított egy hang közvetítést. Várja meg a közvetítés végét az új indításához.", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Nincs jogosultsága hang közvetítést indítani ebben a szobában. Vegye fel a kapcsolatot a szoba adminisztrátorával a szükséges jogosultság megszerzéséhez.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Egy hang közvetítés már folyamatban van. Először fejezze be a jelenlegi közvetítést egy új indításához.", + "Can't start a new voice broadcast": "Az új hang közvetítés nem indítható el", + "Completing set up of your new device": "Új eszköz beállításának elvégzése", + "Waiting for device to sign in": "Várakozás a másik eszköz bejelentkezésére", + "Connecting...": "Kapcsolás…", + "Select 'Scan QR code'": "Válassza ezt: „QR kód beolvasása”", + "Start at the sign in screen": "Kezdje a bejelentkező képernyőn", + "Scan the QR code below with your device that's signed out.": "A kijelentkezett eszközzel olvasd be a QR kódot alább.", + "By approving access for this device, it will have full access to your account.": "Ennek az eszköznek a hozzáférés engedélyezése után az eszköznek teljes hozzáférése lesz a fiókjához.", + "Check that the code below matches with your other device:": "Ellenőrizze, hogy az alábbi kód megegyezik a másik eszközödön lévővel:", + "Devices connected": "Összekötött eszközök", + "The homeserver doesn't support signing in another device.": "A matrix szerver nem támogatja más eszköz bejelentkeztetését.", + "An unexpected error occurred.": "Nemvárt hiba történt.", + "The request was cancelled.": "A kérés megszakítva.", + "The other device isn't signed in.": "A másik eszköz még nincs bejelentkezve.", + "The other device is already signed in.": "A másik eszköz már bejelentkezett.", + "The request was declined on the other device.": "A kérést elutasították a másik eszközön.", + "Linking with this device is not supported.": "Összekötés ezzel az eszközzel nem támogatott.", + "The scanned code is invalid.": "A beolvasott kód érvénytelen.", + "The linking wasn't completed in the required time.": "Az összekötés az elvárt időn belül nem fejeződött be.", + "Sign in new device": "Új eszköz bejelentkeztetése", + "Show QR code": "QR kód beolvasása", + "Sign in with QR code": "Belépés QR kóddal", + "Browser": "Böngésző", + "Yes, stop broadcast": "Igen, közvetítés megállítása" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 08ea1f9234..6c4c692e65 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2267,8 +2267,8 @@ "Value": "Valore", "Setting ID": "ID impostazione", "Show chat effects (animations when receiving e.g. confetti)": "Mostra effetti chat (animazioni quando si ricevono ad es. coriandoli)", - "Original event source": "Fonte dell'evento originale", - "Decrypted event source": "Fonte dell'evento decifrato", + "Original event source": "Sorgente dell'evento originale", + "Decrypted event source": "Sorgente dell'evento decifrato", "Inviting...": "Invito...", "Invite by username": "Invita per nome utente", "Invite your teammates": "Invita la tua squadra", @@ -3635,5 +3635,8 @@ "stop voice broadcast": "ferma broadcast voce", "Yes, stop broadcast": "Sì, ferma il broadcast", "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Vuoi davvero fermare il tuo broadcast in diretta? Verrà terminato il broadcast e la registrazione completa sarà disponibile nella stanza.", - "Stop live broadcasting?": "Fermare il broadcast in diretta?" + "Stop live broadcasting?": "Fermare il broadcast in diretta?", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Non hai l'autorizzazione necessaria per iniziare un broadcast vocale in questa stanza. Contatta un amministratore della stanza per aggiornare le tue autorizzazioni.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Stai già registrando un broadcast vocale. Termina quello in corso per iniziarne uno nuovo.", + "Can't start a new voice broadcast": "Impossibile iniziare un nuovo broadcast vocale" } diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 0a7596d94e..dd4555caf6 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -3632,5 +3632,37 @@ "stop voice broadcast": "zastaviť hlasové vysielanie", "resume voice broadcast": "obnoviť hlasové vysielanie", "pause voice broadcast": "pozastaviť hlasové vysielanie", - "Notifications silenced": "Oznámenia stlmené" + "Notifications silenced": "Oznámenia stlmené", + "Completing set up of your new device": "Dokončenie nastavenia nového zariadenia", + "Waiting for device to sign in": "Čaká sa na prihlásenie zariadenia", + "Connecting...": "Pripájanie…", + "Review and approve the sign in": "Skontrolujte a schváľte prihlásenie", + "Select 'Scan QR code'": "Vyberte možnosť \"Skenovať QR kód\"", + "Start at the sign in screen": "Začnite na prihlasovacej obrazovke", + "Scan the QR code below with your device that's signed out.": "Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené.", + "By approving access for this device, it will have full access to your account.": "Schválením prístupu pre toto zariadenie bude mať plný prístup k vášmu účtu.", + "Check that the code below matches with your other device:": "Skontrolujte, či sa nižšie uvedený kód zhoduje s vaším druhým zariadením:", + "Devices connected": "Zariadenia pripojené", + "The homeserver doesn't support signing in another device.": "Domovský server nepodporuje prihlasovanie do iného zariadenia.", + "An unexpected error occurred.": "Vyskytla sa neočakávaná chyba.", + "The request was cancelled.": "Žiadosť bola zrušená.", + "The other device isn't signed in.": "Druhé zariadenie nie je prihlásené.", + "The other device is already signed in.": "Druhé zariadenie je už prihlásené.", + "The request was declined on the other device.": "Žiadosť bola na druhom zariadení zamietnutá.", + "Linking with this device is not supported.": "Prepojenie s týmto zariadením nie je podporované.", + "The scanned code is invalid.": "Naskenovaný kód je neplatný.", + "The linking wasn't completed in the required time.": "Prepojenie nebolo dokončené v požadovanom čase.", + "Sign in new device": "Prihlásiť nové zariadenie", + "Show QR code": "Zobraziť QR kód", + "You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.": "Toto zariadenie môžete použiť na prihlásenie nového zariadenia pomocou QR kódu. QR kód zobrazený na tomto zariadení musíte naskenovať pomocou zariadenia, ktoré je odhlásené.", + "Sign in with QR code": "Prihlásiť sa pomocou QR kódu", + "Browser": "Prehliadač", + "Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Umožniť zobrazenie QR kódu v správcovi relácií na prihlásenie do iného zariadenia (vyžaduje kompatibilný domovský server)", + "Yes, stop broadcast": "Áno, zastaviť vysielanie", + "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Určite chcete zastaviť vaše vysielanie naživo? Tým sa vysielanie ukončí a v miestnosti bude k dispozícii celý záznam.", + "Stop live broadcasting?": "Zastaviť vysielanie naživo?", + "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Niekto iný už nahráva hlasové vysielanie. Počkajte, kým sa skončí jeho hlasové vysielanie, a potom spustite nové.", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Nemáte požadované oprávnenia na spustenie hlasového vysielania v tejto miestnosti. Obráťte sa na správcu miestnosti, aby vám rozšíril oprávnenia.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Už nahrávate hlasové vysielanie. Ukončite aktuálne hlasové vysielanie a spustite nové.", + "Can't start a new voice broadcast": "Nemôžete spustiť nové hlasové vysielanie" } diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 04b1c056e9..0d2adc0ad8 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -3632,5 +3632,37 @@ "pause voice broadcast": "призупинити голосове мовлення", "You have already joined this call from another device": "Ви вже приєдналися до цього виклику з іншого пристрою", "stop voice broadcast": "припинити голосове мовлення", - "Notifications silenced": "Сповіщення стишено" + "Notifications silenced": "Сповіщення стишено", + "Sign in with QR code": "Увійти за допомогою QR-коду", + "Browser": "Браузер", + "Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Дозволити показ QR-коду в менеджері сеансів для входу на іншому пристрої (потрібен сумісний домашній сервер)", + "Yes, stop broadcast": "Так, припинити мовлення", + "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Ви впевнені, що хочете припинити голосове мовлення? На цьому трансляція завершиться, і повний запис буде доступний у кімнаті.", + "Stop live broadcasting?": "Припинити голосове мовлення?", + "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Хтось інший вже записує голосову трансляцію. Зачекайте, поки вона завершиться, щоб почати нову.", + "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Ви не маєте необхідних дозволів для початку голосового мовлення в цій кімнаті. Зверніться до адміністратора кімнати, щоб оновити ваші дозволи.", + "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Ви вже записуєте голосову трансляцію. Завершіть поточну трансляцію, щоб розпочати нову.", + "Can't start a new voice broadcast": "Не вдалося розпочати нову голосове мовлення", + "Completing set up of your new device": "Завершення налаштування нового пристрою", + "Waiting for device to sign in": "Очікування входу з пристрою", + "Connecting...": "З'єднання...", + "Review and approve the sign in": "Розглянути та схвалити вхід", + "Select 'Scan QR code'": "Виберіть «Сканувати QR-код»", + "Start at the sign in screen": "Почніть з екрана входу", + "Scan the QR code below with your device that's signed out.": "Скануйте QR-код знизу своїм пристроєм, на якому ви вийшли.", + "By approving access for this device, it will have full access to your account.": "Затвердивши доступ для цього пристрою, ви надасте йому повний доступ до вашого облікового запису.", + "Check that the code below matches with your other device:": "Перевірте, чи збігається наведений внизу код з кодом на вашому іншому пристрої:", + "Devices connected": "Пристрої під'єднано", + "The homeserver doesn't support signing in another device.": "Домашній сервер не підтримує вхід на іншому пристрої.", + "An unexpected error occurred.": "Виникла непередбачувана помилка.", + "The request was cancelled.": "Запит було скасовано.", + "The other device isn't signed in.": "На іншому пристрої вхід не виконано.", + "The other device is already signed in.": "На іншому пристрої вхід було виконано.", + "The request was declined on the other device.": "На іншому пристрої запит відхилено.", + "Linking with this device is not supported.": "Зв'язок з цим пристроєм не підтримується.", + "The scanned code is invalid.": "Сканований код недійсний.", + "The linking wasn't completed in the required time.": "У встановлені терміни з'єднання не було виконано.", + "Sign in new device": "Увійти на новому пристрої", + "Show QR code": "Показати QR-код", + "You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.": "Ви можете використовувати цей пристрій для входу на новому пристрої за допомогою QR-коду. Вам потрібно буде сканувати QR-код, показаний на цьому пристрої, своїм пристроєм, на якому ви вийшли." } From 17c3fb89c113a2924c7ddf3969fd8f8cc44feea2 Mon Sep 17 00:00:00 2001 From: kegsay Date: Wed, 19 Oct 2022 21:00:53 +0100 Subject: [PATCH 07/18] Store refactor: convert WidgetPermissionStore (#9458) * Store refactor: convert WidgetPermissionStore Add Jest tests as well. * More tests * Review comments --- .../dialogs/WidgetOpenIDPermissionsDialog.tsx | 5 +- src/contexts/SDKContext.ts | 8 ++ src/stores/widgets/StopGapWidgetDriver.ts | 4 +- src/stores/widgets/WidgetPermissionStore.ts | 20 ++-- test/TestSdkContext.ts | 2 + .../widgets/WidgetPermissionStore-test.ts | 107 ++++++++++++++++++ 6 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 test/stores/widgets/WidgetPermissionStore-test.ts diff --git a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx index 68c2991ed8..2d2d638af9 100644 --- a/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx +++ b/src/components/views/dialogs/WidgetOpenIDPermissionsDialog.tsx @@ -21,10 +21,11 @@ import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../languageHandler"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; -import { OIDCState, WidgetPermissionStore } from "../../../stores/widgets/WidgetPermissionStore"; +import { OIDCState } from "../../../stores/widgets/WidgetPermissionStore"; import { IDialogProps } from "./IDialogProps"; import BaseDialog from "./BaseDialog"; import DialogButtons from "../elements/DialogButtons"; +import { SdkContextClass } from '../../../contexts/SDKContext'; interface IProps extends IDialogProps { widget: Widget; @@ -57,7 +58,7 @@ export default class WidgetOpenIDPermissionsDialog extends React.PureComponent(undefined); @@ -51,6 +52,7 @@ export class SdkContextClass { public client?: MatrixClient; // All protected fields to make it easier to derive test stores + protected _WidgetPermissionStore?: WidgetPermissionStore; protected _RightPanelStore?: RightPanelStore; protected _RoomNotificationStateStore?: RoomNotificationStateStore; protected _RoomViewStore?: RoomViewStore; @@ -102,6 +104,12 @@ export class SdkContextClass { } return this._WidgetLayoutStore; } + public get widgetPermissionStore(): WidgetPermissionStore { + if (!this._WidgetPermissionStore) { + this._WidgetPermissionStore = new WidgetPermissionStore(this); + } + return this._WidgetPermissionStore; + } public get widgetStore(): WidgetStore { if (!this._WidgetStore) { this._WidgetStore = WidgetStore.instance; diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index ba01a10926..ff2619ad59 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -47,7 +47,7 @@ import Modal from "../../Modal"; import WidgetOpenIDPermissionsDialog from "../../components/views/dialogs/WidgetOpenIDPermissionsDialog"; import WidgetCapabilitiesPromptDialog from "../../components/views/dialogs/WidgetCapabilitiesPromptDialog"; import { WidgetPermissionCustomisations } from "../../customisations/WidgetPermissions"; -import { OIDCState, WidgetPermissionStore } from "./WidgetPermissionStore"; +import { OIDCState } from "./WidgetPermissionStore"; import { WidgetType } from "../../widgets/WidgetType"; import { CHAT_EFFECTS } from "../../effects"; import { containsEmoji } from "../../effects/utils"; @@ -350,7 +350,7 @@ export class StopGapWidgetDriver extends WidgetDriver { } public async askOpenID(observer: SimpleObservable) { - const oidcState = WidgetPermissionStore.instance.getOIDCState( + const oidcState = SdkContextClass.instance.widgetPermissionStore.getOIDCState( this.forWidget, this.forWidgetKind, this.inRoomId, ); diff --git a/src/stores/widgets/WidgetPermissionStore.ts b/src/stores/widgets/WidgetPermissionStore.ts index 246492333c..fca018ca5c 100644 --- a/src/stores/widgets/WidgetPermissionStore.ts +++ b/src/stores/widgets/WidgetPermissionStore.ts @@ -17,8 +17,8 @@ import { Widget, WidgetKind } from "matrix-widget-api"; import SettingsStore from "../../settings/SettingsStore"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; import { SettingLevel } from "../../settings/SettingLevel"; +import { SdkContextClass } from "../../contexts/SDKContext"; export enum OIDCState { Allowed, // user has set the remembered value as allowed @@ -27,16 +27,7 @@ export enum OIDCState { } export class WidgetPermissionStore { - private static internalInstance: WidgetPermissionStore; - - private constructor() { - } - - public static get instance(): WidgetPermissionStore { - if (!WidgetPermissionStore.internalInstance) { - WidgetPermissionStore.internalInstance = new WidgetPermissionStore(); - } - return WidgetPermissionStore.internalInstance; + public constructor(private readonly context: SdkContextClass) { } // TODO (all functions here): Merge widgetKind with the widget definition @@ -44,7 +35,7 @@ export class WidgetPermissionStore { private packSettingKey(widget: Widget, kind: WidgetKind, roomId?: string): string { let location = roomId; if (kind !== WidgetKind.Room) { - location = MatrixClientPeg.get().getUserId(); + location = this.context.client?.getUserId(); } if (kind === WidgetKind.Modal) { location = '*MODAL*-' + location; // to guarantee differentiation from whatever spawned it @@ -71,7 +62,10 @@ export class WidgetPermissionStore { public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string, newState: OIDCState) { const settingsKey = this.packSettingKey(widget, kind, roomId); - const currentValues = SettingsStore.getValue("widgetOpenIDPermissions"); + let currentValues = SettingsStore.getValue("widgetOpenIDPermissions"); + if (!currentValues) { + currentValues = {}; + } if (!currentValues.allow) currentValues.allow = []; if (!currentValues.deny) currentValues.deny = []; diff --git a/test/TestSdkContext.ts b/test/TestSdkContext.ts index 137b71f9a3..4ce9100a94 100644 --- a/test/TestSdkContext.ts +++ b/test/TestSdkContext.ts @@ -22,6 +22,7 @@ import RightPanelStore from "../src/stores/right-panel/RightPanelStore"; import { RoomViewStore } from "../src/stores/RoomViewStore"; import { SpaceStoreClass } from "../src/stores/spaces/SpaceStore"; import { WidgetLayoutStore } from "../src/stores/widgets/WidgetLayoutStore"; +import { WidgetPermissionStore } from "../src/stores/widgets/WidgetPermissionStore"; import WidgetStore from "../src/stores/WidgetStore"; /** @@ -32,6 +33,7 @@ export class TestSdkContext extends SdkContextClass { public _RightPanelStore?: RightPanelStore; public _RoomNotificationStateStore?: RoomNotificationStateStore; public _RoomViewStore?: RoomViewStore; + public _WidgetPermissionStore?: WidgetPermissionStore; public _WidgetLayoutStore?: WidgetLayoutStore; public _WidgetStore?: WidgetStore; public _PosthogAnalytics?: PosthogAnalytics; diff --git a/test/stores/widgets/WidgetPermissionStore-test.ts b/test/stores/widgets/WidgetPermissionStore-test.ts new file mode 100644 index 0000000000..3ebb7fc9f5 --- /dev/null +++ b/test/stores/widgets/WidgetPermissionStore-test.ts @@ -0,0 +1,107 @@ +/* +Copyright 2022 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 { mocked } from "jest-mock"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { Widget, WidgetKind } from "matrix-widget-api"; + +import { OIDCState, WidgetPermissionStore } from "../../../src/stores/widgets/WidgetPermissionStore"; +import SettingsStore from "../../../src/settings/SettingsStore"; +import { TestSdkContext } from "../../TestSdkContext"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import { SdkContextClass } from "../../../src/contexts/SDKContext"; +import { stubClient } from "../../test-utils"; + +jest.mock("../../../src/settings/SettingsStore"); + +describe("WidgetPermissionStore", () => { + let widgetPermissionStore: WidgetPermissionStore; + let mockClient: MatrixClient; + const userId = "@alice:localhost"; + const roomId = "!room:localhost"; + const w = new Widget({ + id: "wid", + creatorUserId: userId, + type: "m.custom", + url: "https://invalid.address.here", + }); + let settings = {}; // key value store + + beforeEach(() => { + settings = {}; // clear settings + mocked(SettingsStore.getValue).mockImplementation((setting: string) => { + return settings[setting]; + }); + mocked(SettingsStore.setValue).mockImplementation((settingName: string, + roomId: string | null, + level: SettingLevel, + value: any, + ): Promise => { + // the store doesn't use any specific level or room ID (room IDs are packed into keys in `value`) + settings[settingName] = value; + return Promise.resolve(); + }); + mockClient = stubClient(); + const context = new TestSdkContext(); + context.client = mockClient; + widgetPermissionStore = new WidgetPermissionStore(context); + }); + + it("should persist OIDCState.Allowed for a widget", () => { + widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed); + // check it remembered the value + expect( + widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null), + ).toEqual(OIDCState.Allowed); + }); + + it("should persist OIDCState.Denied for a widget", () => { + widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied); + // check it remembered the value + expect( + widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null), + ).toEqual(OIDCState.Denied); + }); + + it("should update OIDCState for a widget", () => { + widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed); + widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied); + // check it remembered the latest value + expect( + widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null), + ).toEqual(OIDCState.Denied); + }); + + it("should scope the location for a widget when setting OIDC state", () => { + // allow this widget for this room + widgetPermissionStore.setOIDCState(w, WidgetKind.Room, roomId, OIDCState.Allowed); + // check it remembered the value + expect( + widgetPermissionStore.getOIDCState(w, WidgetKind.Room, roomId), + ).toEqual(OIDCState.Allowed); + // check this is not the case for the entire account + expect( + widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId), + ).toEqual(OIDCState.Unknown); + }); + it("is created once in SdkContextClass", () => { + const context = new SdkContextClass(); + const store = context.widgetPermissionStore; + expect(store).toBeDefined(); + const store2 = context.widgetPermissionStore; + expect(store2).toStrictEqual(store); + }); +}); From dade38086cb6904d4e8138914f53e792b740a02d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 20 Oct 2022 00:52:06 -0600 Subject: [PATCH 08/18] Fix slightly noisy warning when switching spaces (#9468) Sometimes `spaceName` can be `undefined` because of function timing - use a different method for getting the space's name when this happens. --- src/components/views/rooms/RoomListHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomListHeader.tsx b/src/components/views/rooms/RoomListHeader.tsx index f783e628f3..9c7b5c11aa 100644 --- a/src/components/views/rooms/RoomListHeader.tsx +++ b/src/components/views/rooms/RoomListHeader.tsx @@ -379,7 +379,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => { isExpanded={mainMenuDisplayed} className="mx_RoomListHeader_contextMenuButton" title={activeSpace - ? _t("%(spaceName)s menu", { spaceName }) + ? _t("%(spaceName)s menu", { spaceName: spaceName ?? activeSpace.name }) : _t("Home options")} > { title } From 6fe8744e4dfd8f9661f7dc92719cac5d69841142 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 20 Oct 2022 00:52:17 -0600 Subject: [PATCH 09/18] Remove performance metrics from cypress (#9467) They are unfortunately unreliable and have been broken since June anyways. --- .github/workflows/cypress.yaml | 44 ------------ cypress/e2e/create-room/create-room.spec.ts | 2 - cypress/e2e/login/login.spec.ts | 2 - cypress/e2e/register/register.spec.ts | 4 -- cypress/plugins/index.ts | 2 - cypress/plugins/performance.ts | 47 ------------- cypress/support/e2e.ts | 1 - cypress/support/performance.ts | 74 --------------------- 8 files changed, 176 deletions(-) delete mode 100644 cypress/plugins/performance.ts delete mode 100644 cypress/support/performance.ts diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index a76c00918b..57e6a7837e 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -155,17 +155,6 @@ jobs: cypress/videos cypress/synapselogs - - run: mv cypress/performance/*.json cypress/performance/measurements-${{ strategy.job-index }}.json - continue-on-error: true - - - name: Upload Benchmark - uses: actions/upload-artifact@v2 - with: - name: cypress-benchmark - path: cypress/performance/* - if-no-files-found: ignore - retention-days: 1 - report: name: Report results needs: tests @@ -181,36 +170,3 @@ jobs: context: ${{ github.workflow }} / cypress (${{ github.event.workflow_run.event }} => ${{ github.event_name }}) sha: ${{ github.event.workflow_run.head_sha }} target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} - - store-benchmark: - needs: tests - runs-on: ubuntu-latest - if: | - github.event.workflow_run.event != 'pull_request' && - github.event.workflow_run.head_branch == 'develop' && - github.event.workflow_run.head_repository.full_name == github.repository - permissions: - contents: write - steps: - - uses: actions/checkout@v2 - - - name: Download benchmark result - uses: actions/download-artifact@v3 - with: - name: cypress-benchmark - - - name: Merge measurements - run: jq -s add measurements-*.json > measurements.json - - - name: Store benchmark result - uses: matrix-org/github-action-benchmark@jsperfentry-6 - with: - name: Cypress measurements - tool: 'jsperformanceentry' - output-file-path: measurements.json - # The dashboard is available at https://matrix-org.github.io/matrix-react-sdk/cypress/bench/ - benchmark-data-dir-path: cypress/bench - fail-on-alert: false - comment-on-alert: false - github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: ${{ github.event.workflow_run.event != 'pull_request' }} diff --git a/cypress/e2e/create-room/create-room.spec.ts b/cypress/e2e/create-room/create-room.spec.ts index deac0728e3..1217c917b6 100644 --- a/cypress/e2e/create-room/create-room.spec.ts +++ b/cypress/e2e/create-room/create-room.spec.ts @@ -54,12 +54,10 @@ describe("Create Room", () => { // Fill room address cy.get('[label="Room address"]').type("test-room-1"); // Submit - cy.startMeasuring("from-submit-to-room"); cy.get(".mx_Dialog_primary").click(); }); cy.url().should("contain", "/#/room/#test-room-1:localhost"); - cy.stopMeasuring("from-submit-to-room"); cy.contains(".mx_RoomHeader_nametext", name); cy.contains(".mx_RoomHeader_topic", topic); }); diff --git a/cypress/e2e/login/login.spec.ts b/cypress/e2e/login/login.spec.ts index ff963dfbfe..1058287010 100644 --- a/cypress/e2e/login/login.spec.ts +++ b/cypress/e2e/login/login.spec.ts @@ -52,11 +52,9 @@ describe("Login", () => { cy.get("#mx_LoginForm_username").type(username); cy.get("#mx_LoginForm_password").type(password); - cy.startMeasuring("from-submit-to-home"); cy.get(".mx_Login_submit").click(); cy.url().should('contain', '/#/home', { timeout: 30000 }); - cy.stopMeasuring("from-submit-to-home"); }); }); diff --git a/cypress/e2e/register/register.spec.ts b/cypress/e2e/register/register.spec.ts index 1945eb7fec..98ef2bd729 100644 --- a/cypress/e2e/register/register.spec.ts +++ b/cypress/e2e/register/register.spec.ts @@ -55,7 +55,6 @@ describe("Registration", () => { cy.get("#mx_RegistrationForm_username").type("alice"); cy.get("#mx_RegistrationForm_password").type("totally a great password"); cy.get("#mx_RegistrationForm_passwordConfirm").type("totally a great password"); - cy.startMeasuring("create-account"); cy.get(".mx_Login_submit").click(); cy.get(".mx_RegistrationEmailPromptDialog").should("be.visible"); @@ -63,13 +62,11 @@ describe("Registration", () => { cy.checkA11y(); cy.get(".mx_RegistrationEmailPromptDialog button.mx_Dialog_primary").click(); - cy.stopMeasuring("create-account"); cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy").should("be.visible"); cy.percySnapshot("Registration terms prompt", { percyCSS }); cy.checkA11y(); cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy input").click(); - cy.startMeasuring("from-submit-to-home"); cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click(); cy.get(".mx_UseCaseSelection_skip", { timeout: 30000 }).should("exist"); @@ -78,7 +75,6 @@ describe("Registration", () => { cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click(); cy.url().should('contain', '/#/home'); - cy.stopMeasuring("from-submit-to-home"); cy.get('[aria-label="User menu"]').click(); cy.get('[aria-label="Security & Privacy"]').click(); diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index 09b2bdb53b..ce154ee0bc 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -18,7 +18,6 @@ limitations under the License. import PluginEvents = Cypress.PluginEvents; import PluginConfigOptions = Cypress.PluginConfigOptions; -import { performance } from "./performance"; import { synapseDocker } from "./synapsedocker"; import { slidingSyncProxyDocker } from "./sliding-sync"; import { webserver } from "./webserver"; @@ -30,7 +29,6 @@ import { log } from "./log"; */ export default function(on: PluginEvents, config: PluginConfigOptions) { docker(on, config); - performance(on, config); synapseDocker(on, config); slidingSyncProxyDocker(on, config); webserver(on, config); diff --git a/cypress/plugins/performance.ts b/cypress/plugins/performance.ts deleted file mode 100644 index c6bd3e4ce9..0000000000 --- a/cypress/plugins/performance.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2022 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 * as path from "path"; -import * as fse from "fs-extra"; - -import PluginEvents = Cypress.PluginEvents; -import PluginConfigOptions = Cypress.PluginConfigOptions; - -// This holds all the performance measurements throughout the run -let bufferedMeasurements: PerformanceEntry[] = []; - -function addMeasurements(measurements: PerformanceEntry[]): void { - bufferedMeasurements = bufferedMeasurements.concat(measurements); - return null; -} - -async function writeMeasurementsFile() { - try { - const measurementsPath = path.join("cypress", "performance", "measurements.json"); - await fse.outputJSON(measurementsPath, bufferedMeasurements, { - spaces: 4, - }); - } finally { - bufferedMeasurements = []; - } -} - -export function performance(on: PluginEvents, config: PluginConfigOptions) { - on("task", { addMeasurements }); - on("after:run", writeMeasurementsFile); -} diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 899d41c5b8..4470c2192e 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -19,7 +19,6 @@ limitations under the License. import "@percy/cypress"; import "cypress-real-events"; -import "./performance"; import "./synapse"; import "./login"; import "./labs"; diff --git a/cypress/support/performance.ts b/cypress/support/performance.ts deleted file mode 100644 index bbd1fe217d..0000000000 --- a/cypress/support/performance.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2022 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 Chainable = Cypress.Chainable; -import AUTWindow = Cypress.AUTWindow; - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - /** - * Start measuring the duration of some task. - * @param task The task name. - */ - startMeasuring(task: string): Chainable; - /** - * Stop measuring the duration of some task. - * The duration is reported in the Cypress log. - * @param task The task name. - */ - stopMeasuring(task: string): Chainable; - } - } -} - -function getPrefix(task: string): string { - return `cy:${Cypress.spec.name.split(".")[0]}:${task}`; -} - -function startMeasuring(task: string): Chainable { - return cy.window({ log: false }).then((win) => { - win.mxPerformanceMonitor.start(getPrefix(task)); - }); -} - -function stopMeasuring(task: string): Chainable { - return cy.window({ log: false }).then((win) => { - const measure = win.mxPerformanceMonitor.stop(getPrefix(task)); - cy.log(`**${task}** ${measure.duration} ms`); - }); -} - -Cypress.Commands.add("startMeasuring", startMeasuring); -Cypress.Commands.add("stopMeasuring", stopMeasuring); - -Cypress.on("window:before:unload", (event: BeforeUnloadEvent) => { - const doc = event.target as Document; - if (doc.location.href === "about:blank") return; - const win = doc.defaultView as AUTWindow; - if (!win.mxPerformanceMonitor) return; - const entries = win.mxPerformanceMonitor.getEntries().filter(entry => { - return entry.name.startsWith("cy:"); - }); - if (!entries || entries.length === 0) return; - cy.task("addMeasurements", entries); -}); - -// Needed to make this file a module -export { }; From 3c9ba3e69f743145bf4caf158738a2278a4b0f2b Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Thu, 20 Oct 2022 10:04:14 +0200 Subject: [PATCH 10/18] Replace Icon with webpack loaded SVG (#9464) --- res/css/_components.pcss | 3 +- .../{components/atoms => compound}/_Icon.pcss | 23 +--- .../atoms/_VoiceBroadcastControl.pcss | 27 ++++ res/img/element-icons/Stop.svg | 2 +- res/img/element-icons/live.svg | 41 +----- res/img/element-icons/pause.svg | 4 +- res/img/element-icons/play.svg | 2 +- res/img/voip/call-view/mic-on.svg | 2 +- src/components/atoms/Icon.tsx | 83 ------------ src/i18n/strings/en_EN.json | 6 +- .../components/atoms/LiveBadge.tsx | 4 +- .../atoms/PlaybackControlButton.tsx | 53 -------- ...opButton.tsx => VoiceBroadcastControl.tsx} | 17 ++- .../components/atoms/VoiceBroadcastHeader.tsx | 7 +- .../molecules/VoiceBroadcastPlaybackBody.tsx | 37 +++++- .../molecules/VoiceBroadcastRecordingPip.tsx | 9 +- src/voice-broadcast/index.ts | 3 +- test/components/atoms/Icon-test.tsx | 47 ------- .../atoms/__snapshots__/Icon-test.tsx.snap | 34 ----- .../atoms/PlaybackControlButton-test.tsx | 45 ------- .../components/atoms/StopButton-test.tsx | 45 ------- .../atoms/VoiceBroadcastControl-test.tsx | 55 ++++++++ .../__snapshots__/LiveBadge-test.tsx.snap | 7 +- .../PlaybackControlButton-test.tsx.snap | 55 -------- .../__snapshots__/StopButton-test.tsx.snap | 19 --- .../VoiceBroadcastControl-test.tsx.snap | 16 +++ .../VoiceBroadcastHeader-test.tsx.snap | 28 ++-- .../VoiceBroadcastPlaybackBody-test.tsx | 26 ++-- .../VoiceBroadcastRecordingPip-test.tsx | 2 +- .../VoiceBroadcastPlaybackBody-test.tsx.snap | 121 ++++++++++++------ .../VoiceBroadcastRecordingBody-test.tsx.snap | 14 +- .../VoiceBroadcastRecordingPip-test.tsx.snap | 25 ++-- 32 files changed, 298 insertions(+), 564 deletions(-) rename res/css/{components/atoms => compound}/_Icon.pcss (68%) create mode 100644 res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss delete mode 100644 src/components/atoms/Icon.tsx delete mode 100644 src/voice-broadcast/components/atoms/PlaybackControlButton.tsx rename src/voice-broadcast/components/atoms/{StopButton.tsx => VoiceBroadcastControl.tsx} (68%) delete mode 100644 test/components/atoms/Icon-test.tsx delete mode 100644 test/components/atoms/__snapshots__/Icon-test.tsx.snap delete mode 100644 test/voice-broadcast/components/atoms/PlaybackControlButton-test.tsx delete mode 100644 test/voice-broadcast/components/atoms/StopButton-test.tsx create mode 100644 test/voice-broadcast/components/atoms/VoiceBroadcastControl-test.tsx delete mode 100644 test/voice-broadcast/components/atoms/__snapshots__/PlaybackControlButton-test.tsx.snap delete mode 100644 test/voice-broadcast/components/atoms/__snapshots__/StopButton-test.tsx.snap create mode 100644 test/voice-broadcast/components/atoms/__snapshots__/VoiceBroadcastControl-test.tsx.snap diff --git a/res/css/_components.pcss b/res/css/_components.pcss index b2fcb0dd4f..819afe64a4 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -4,7 +4,7 @@ @import "./_font-sizes.pcss"; @import "./_font-weights.pcss"; @import "./_spacing.pcss"; -@import "./components/atoms/_Icon.pcss"; +@import "./compound/_Icon.pcss"; @import "./components/views/beacon/_BeaconListItem.pcss"; @import "./components/views/beacon/_BeaconStatus.pcss"; @import "./components/views/beacon/_BeaconStatusTooltip.pcss"; @@ -369,6 +369,7 @@ @import "./views/voip/_VideoFeed.pcss"; @import "./voice-broadcast/atoms/_LiveBadge.pcss"; @import "./voice-broadcast/atoms/_PlaybackControlButton.pcss"; +@import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; @import "./voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss"; @import "./voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss"; diff --git a/res/css/components/atoms/_Icon.pcss b/res/css/compound/_Icon.pcss similarity index 68% rename from res/css/components/atoms/_Icon.pcss rename to res/css/compound/_Icon.pcss index b9d994e43f..88f49f9da0 100644 --- a/res/css/components/atoms/_Icon.pcss +++ b/res/css/compound/_Icon.pcss @@ -14,13 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +/* + * Compound icon + + * {@link https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed} + */ + .mx_Icon { box-sizing: border-box; - display: inline-block; - mask-origin: content-box; - mask-position: center; - mask-repeat: no-repeat; - mask-size: contain; padding: 1px; } @@ -28,15 +29,3 @@ limitations under the License. height: 16px; width: 16px; } - -.mx_Icon_accent { - background-color: $accent; -} - -.mx_Icon_live-badge { - background-color: #fff; -} - -.mx_Icon_compound-secondary-content { - background-color: $secondary-content; -} diff --git a/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss b/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss new file mode 100644 index 0000000000..bf07157a68 --- /dev/null +++ b/res/css/voice-broadcast/atoms/_VoiceBroadcastControl.pcss @@ -0,0 +1,27 @@ +/* +Copyright 2022 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_VoiceBroadcastControl { + align-items: center; + background-color: $background; + border-radius: 50%; + color: $secondary-content; + display: flex; + height: 32px; + justify-content: center; + margin-bottom: $spacing-8; + width: 32px; +} diff --git a/res/img/element-icons/Stop.svg b/res/img/element-icons/Stop.svg index 29c7a0cef7..d63459e1db 100644 --- a/res/img/element-icons/Stop.svg +++ b/res/img/element-icons/Stop.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/live.svg b/res/img/element-icons/live.svg index 40a7a66677..31341f1ef6 100644 --- a/res/img/element-icons/live.svg +++ b/res/img/element-icons/live.svg @@ -5,54 +5,23 @@ viewBox="0 0 21.799 21.799" fill="none" version="1.1" - id="svg12" - sodipodi:docname="live.svg" - inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - - + fill="currentColor" /> + fill="currentColor" /> + fill="currentColor" /> + fill="currentColor" /> + fill="currentColor" /> diff --git a/res/img/element-icons/pause.svg b/res/img/element-icons/pause.svg index 293c0a10d8..4b7be99e3b 100644 --- a/res/img/element-icons/pause.svg +++ b/res/img/element-icons/pause.svg @@ -1,4 +1,4 @@ - - + + diff --git a/res/img/element-icons/play.svg b/res/img/element-icons/play.svg index 339e20b729..3443ae01fa 100644 --- a/res/img/element-icons/play.svg +++ b/res/img/element-icons/play.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/voip/call-view/mic-on.svg b/res/img/voip/call-view/mic-on.svg index 57428a3cd8..317d10b296 100644 --- a/res/img/voip/call-view/mic-on.svg +++ b/res/img/voip/call-view/mic-on.svg @@ -1,3 +1,3 @@ - + diff --git a/src/components/atoms/Icon.tsx b/src/components/atoms/Icon.tsx deleted file mode 100644 index 56d8236250..0000000000 --- a/src/components/atoms/Icon.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright 2022 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 liveIcon from "../../../res/img/element-icons/live.svg"; -import microphoneIcon from "../../../res/img/voip/call-view/mic-on.svg"; -import pauseIcon from "../../../res/img/element-icons/pause.svg"; -import playIcon from "../../../res/img/element-icons/play.svg"; -import stopIcon from "../../../res/img/element-icons/Stop.svg"; - -export enum IconType { - Live, - Microphone, - Pause, - Play, - Stop, -} - -const iconTypeMap = new Map([ - [IconType.Live, liveIcon], - [IconType.Microphone, microphoneIcon], - [IconType.Pause, pauseIcon], - [IconType.Play, playIcon], - [IconType.Stop, stopIcon], -]); - -export enum IconColour { - Accent = "accent", - LiveBadge = "live-badge", - CompoundSecondaryContent = "compound-secondary-content", -} - -export enum IconSize { - S16 = "16", -} - -interface IconProps { - colour?: IconColour; - size?: IconSize; - type: IconType; -} - -export const Icon: React.FC = ({ - size = IconSize.S16, - colour = IconColour.Accent, - type, - ...rest -}) => { - const classes = [ - "mx_Icon", - `mx_Icon_${size}`, - `mx_Icon_${colour}`, - ]; - - const styles: React.CSSProperties = { - maskImage: `url("${iconTypeMap.get(type)}")`, - WebkitMaskImage: `url("${iconTypeMap.get(type)}")`, - }; - - return ( - - ); -}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f322c5de8d..f40e165804 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -644,10 +644,10 @@ "Stop live broadcasting?": "Stop live broadcasting?", "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.", "Yes, stop broadcast": "Yes, stop broadcast", - "Live": "Live", - "pause voice broadcast": "pause voice broadcast", + "play voice broadcast": "play voice broadcast", "resume voice broadcast": "resume voice broadcast", - "stop voice broadcast": "stop voice broadcast", + "pause voice broadcast": "pause voice broadcast", + "Live": "Live", "Voice broadcast": "Voice broadcast", "Cannot reach homeserver": "Cannot reach homeserver", "Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin", diff --git a/src/voice-broadcast/components/atoms/LiveBadge.tsx b/src/voice-broadcast/components/atoms/LiveBadge.tsx index cd2a16e797..ba94aa14a9 100644 --- a/src/voice-broadcast/components/atoms/LiveBadge.tsx +++ b/src/voice-broadcast/components/atoms/LiveBadge.tsx @@ -16,12 +16,12 @@ limitations under the License. import React from "react"; -import { Icon, IconColour, IconType } from "../../../components/atoms/Icon"; +import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg"; import { _t } from "../../../languageHandler"; export const LiveBadge: React.FC = () => { return
- + { _t("Live") }
; }; diff --git a/src/voice-broadcast/components/atoms/PlaybackControlButton.tsx b/src/voice-broadcast/components/atoms/PlaybackControlButton.tsx deleted file mode 100644 index b67e6b3e24..0000000000 --- a/src/voice-broadcast/components/atoms/PlaybackControlButton.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2022 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 { VoiceBroadcastPlaybackState } from "../.."; -import { Icon, IconColour, IconType } from "../../../components/atoms/Icon"; -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; -import { _t } from "../../../languageHandler"; - -const stateIconMap = new Map([ - [VoiceBroadcastPlaybackState.Playing, IconType.Pause], - [VoiceBroadcastPlaybackState.Paused, IconType.Play], - [VoiceBroadcastPlaybackState.Stopped, IconType.Play], -]); - -interface Props { - onClick: () => void; - state: VoiceBroadcastPlaybackState; -} - -export const PlaybackControlButton: React.FC = ({ - onClick, - state, -}) => { - const ariaLabel = state === VoiceBroadcastPlaybackState.Playing - ? _t("pause voice broadcast") - : _t("resume voice broadcast"); - - return - - ; -}; diff --git a/src/voice-broadcast/components/atoms/StopButton.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx similarity index 68% rename from src/voice-broadcast/components/atoms/StopButton.tsx rename to src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx index 50abb209d0..238d138698 100644 --- a/src/voice-broadcast/components/atoms/StopButton.tsx +++ b/src/voice-broadcast/components/atoms/VoiceBroadcastControl.tsx @@ -16,25 +16,24 @@ limitations under the License. import React from "react"; -import { Icon, IconColour, IconType } from "../../../components/atoms/Icon"; import AccessibleButton from "../../../components/views/elements/AccessibleButton"; -import { _t } from "../../../languageHandler"; interface Props { + icon: React.FC>; + label: string; onClick: () => void; } -export const StopButton: React.FC = ({ +export const VoiceBroadcastControl: React.FC = ({ + icon: Icon, + label, onClick, }) => { return - + ; }; diff --git a/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx b/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx index 5abc4d21e4..c83e8e8a0c 100644 --- a/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx +++ b/src/voice-broadcast/components/atoms/VoiceBroadcastHeader.tsx @@ -15,7 +15,8 @@ import React from "react"; import { Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { LiveBadge } from "../.."; -import { Icon, IconColour, IconType } from "../../../components/atoms/Icon"; +import { Icon as LiveIcon } from "../../../../res/img/element-icons/live.svg"; +import { Icon as MicrophoneIcon } from "../../../../res/img/voip/call-view/mic-on.svg"; import { _t } from "../../../languageHandler"; import RoomAvatar from "../../../components/views/avatars/RoomAvatar"; @@ -34,7 +35,7 @@ export const VoiceBroadcastHeader: React.FC = ({ }) => { const broadcast = showBroadcast ?
- + { _t("Voice broadcast") }
: null; @@ -46,7 +47,7 @@ export const VoiceBroadcastHeader: React.FC = ({ { room.name }
- + { sender.name }
{ broadcast } diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx index 035b3ce6e5..e0634636a7 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx @@ -17,13 +17,16 @@ limitations under the License. import React from "react"; import { - PlaybackControlButton, + VoiceBroadcastControl, VoiceBroadcastHeader, VoiceBroadcastPlayback, VoiceBroadcastPlaybackState, } from "../.."; import Spinner from "../../../components/views/elements/Spinner"; import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; +import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg"; +import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg"; +import { _t } from "../../../languageHandler"; interface VoiceBroadcastPlaybackBodyProps { playback: VoiceBroadcastPlayback; @@ -40,9 +43,35 @@ export const VoiceBroadcastPlaybackBody: React.FC - : ; + let control: React.ReactNode; + + if (playbackState === VoiceBroadcastPlaybackState.Buffering) { + control = ; + } else { + let controlIcon: React.FC>; + let controlLabel: string; + + switch (playbackState) { + case VoiceBroadcastPlaybackState.Stopped: + controlIcon = PlayIcon; + controlLabel = _t("play voice broadcast"); + break; + case VoiceBroadcastPlaybackState.Paused: + controlIcon = PlayIcon; + controlLabel = _t("resume voice broadcast"); + break; + case VoiceBroadcastPlaybackState.Playing: + controlIcon = PauseIcon; + controlLabel = _t("pause voice broadcast"); + break; + } + + control = ; + } return (
diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx index c7604b7d90..7178f65965 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx @@ -17,11 +17,12 @@ limitations under the License. import React from "react"; import { - StopButton, + VoiceBroadcastControl, VoiceBroadcastRecording, } from "../.."; import { useVoiceBroadcastRecording } from "../../hooks/useVoiceBroadcastRecording"; import { VoiceBroadcastHeader } from "../atoms/VoiceBroadcastHeader"; +import { Icon as StopIcon } from "../../../../res/img/element-icons/Stop.svg"; interface VoiceBroadcastRecordingPipProps { recording: VoiceBroadcastRecording; @@ -45,7 +46,11 @@ export const VoiceBroadcastRecordingPip: React.FC
- +
; }; diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts index 8f01c089c6..10329e224d 100644 --- a/src/voice-broadcast/index.ts +++ b/src/voice-broadcast/index.ts @@ -26,8 +26,7 @@ export * from "./models/VoiceBroadcastRecording"; export * from "./audio/VoiceBroadcastRecorder"; export * from "./components/VoiceBroadcastBody"; export * from "./components/atoms/LiveBadge"; -export * from "./components/atoms/PlaybackControlButton"; -export * from "./components/atoms/StopButton"; +export * from "./components/atoms/VoiceBroadcastControl"; export * from "./components/atoms/VoiceBroadcastHeader"; export * from "./components/molecules/VoiceBroadcastPlaybackBody"; export * from "./components/molecules/VoiceBroadcastRecordingBody"; diff --git a/test/components/atoms/Icon-test.tsx b/test/components/atoms/Icon-test.tsx deleted file mode 100644 index 57e6e3990c..0000000000 --- a/test/components/atoms/Icon-test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2022 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 { render } from "@testing-library/react"; - -import { Icon, IconColour, IconSize, IconType } from "../../../src/components/atoms/Icon"; - -describe("Icon", () => { - it.each([ - IconColour.Accent, - IconColour.LiveBadge, - ])("should render the colour %s", (colour: IconColour) => { - const { container } = render( - , - ); - expect(container).toMatchSnapshot(); - }); - - it.each([ - IconSize.S16, - ])("should render the size %s", (size: IconSize) => { - const { container } = render( - , - ); - expect(container).toMatchSnapshot(); - }); -}); diff --git a/test/components/atoms/__snapshots__/Icon-test.tsx.snap b/test/components/atoms/__snapshots__/Icon-test.tsx.snap deleted file mode 100644 index c30b4ba332..0000000000 --- a/test/components/atoms/__snapshots__/Icon-test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Icon should render the colour accent 1`] = ` -
-