diff --git a/.github/workflows/deploy-react-sample-apps.yml b/.github/workflows/deploy-react-sample-apps.yml index dad9ca9c98..35ea87778a 100644 --- a/.github/workflows/deploy-react-sample-apps.yml +++ b/.github/workflows/deploy-react-sample-apps.yml @@ -21,6 +21,10 @@ on: - '!**/docusaurus/**' workflow_dispatch: +concurrency: + group: deploy-react-sample-apps-${{ github.ref }} + cancel-in-progress: true + jobs: build-and-deploy-sample-apps: name: Deploy ${{ matrix.application.name }} @@ -30,22 +34,22 @@ jobs: matrix: application: - name: messenger-clone-react - folder: messenger-clone project-id: prj_FNUiw2FtWJEDVHP5XttyLQmGw39n populate-tokens: true - name: zoom-clone-react - folder: zoom-clone project-id: prj_y2GjsUXNvW7MdQ0EpJVG0FBNNovL populate-tokens: true - name: egress-composite project-id: prj_R6DLpP2Gxc0aRHGEopQWE8tveNDX - name: livestream-app-react - folder: livestream-app project-id: prj_uNJTw7DefSAntAoWCXwJaHc1khoA - name: audio-rooms project-id: prj_0WnHcvVkXpM4PRc2ymVmrAHFILoT - - name: react-dogfood - project-id: prj_bUGd9z0sMAMxZmmgMaec3oiRbWzT + - name: react-dogfood - https://pronto.getstream.io + project-id: prj_4TTdjeVHEDhWWiFRfjIr1QFb5ell + - name: react-dogfood - https://getstream.io/video/demos + project-id: prj_tTLn3XMVal4D1Nnel9nPJ0hoI9wA + base-path: /video/demos env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} @@ -87,6 +91,8 @@ jobs: ### Vercel deployment (Preview) ### - name: Vercel Pull/Build/Deploy (Preview) if: ${{ github.ref_name != 'main' }} + env: + NEXT_PUBLIC_BASE_PATH: ${{ matrix.application.base-path || '' }} run: > yarn vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} && yarn vercel build --token=${{ secrets.VERCEL_TOKEN }} && @@ -95,6 +101,8 @@ jobs: ### Vercel deployment (Production) ### - name: Vercel Pull/Build/Deploy (Production) if: ${{ github.ref_name == 'main' }} + env: + NEXT_PUBLIC_BASE_PATH: ${{ matrix.application.base-path || '' }} run: > yarn vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} && yarn vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} && diff --git a/package.json b/package.json index b20417c2b6..89107851d5 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "nx": "16.0.1", "prettier": "^2.8.8", "typescript": "^5.2.2", - "vercel": "^32.4.1", + "vercel": "^33.2.0", "vite": "^4.4.11" } } diff --git a/packages/client/package.json b/packages/client/package.json index 5f28ab032a..c026b99272 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -28,9 +28,9 @@ "CHANGELOG.md" ], "dependencies": { - "@protobuf-ts/runtime": "^2.9.1", - "@protobuf-ts/runtime-rpc": "^2.9.1", - "@protobuf-ts/twirp-transport": "^2.9.1", + "@protobuf-ts/runtime": "^2.9.3", + "@protobuf-ts/runtime-rpc": "^2.9.3", + "@protobuf-ts/twirp-transport": "^2.9.3", "@types/ws": "^8.5.7", "axios": "^1.6.0", "base64-js": "^1.5.1", diff --git a/packages/client/src/__tests__/server-side/call.test.ts b/packages/client/src/__tests__/server-side/call.test.ts index 91a47f4479..fbabcf0c2c 100644 --- a/packages/client/src/__tests__/server-side/call.test.ts +++ b/packages/client/src/__tests__/server-side/call.test.ts @@ -89,12 +89,6 @@ describe('call API', () => { }); expect(response.calls.length).toBeLessThanOrEqual(2); - - response = await client.queryCalls({ - filter_conditions: { backstage: { $eq: false } }, - }); - - expect(response.calls.length).toBeGreaterThanOrEqual(1); }); describe('recording', () => { @@ -150,7 +144,7 @@ describe('call API', () => { ); }); - it('query recordings', async () => { + it.skip('query recordings', async () => { // somewhat dummy test, we should do a proper test in the future let response = await call.queryRecordings(); diff --git a/packages/client/src/gen/google/protobuf/struct.ts b/packages/client/src/gen/google/protobuf/struct.ts index 6fdb1763d8..600a93af62 100644 --- a/packages/client/src/gen/google/protobuf/struct.ts +++ b/packages/client/src/gen/google/protobuf/struct.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// @generated by protobuf-ts 2.9.0 with parameter long_type_string,client_generic,server_none,eslint_disable +// @generated by protobuf-ts 2.9.3 with parameter long_type_string,client_generic,server_none,eslint_disable // @generated from protobuf file "google/protobuf/struct.proto" (package "google.protobuf", syntax proto3) // tslint:disable // @@ -46,7 +46,6 @@ import type { } from '@protobuf-ts/runtime'; import { isJsonObject, - MESSAGE_TYPE, MessageType, reflectionMergePartial, typeofJsonValue, @@ -226,11 +225,8 @@ class Struct$Type extends MessageType { return target; } create(value?: PartialMessage): Struct { - const message = { fields: {} }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.fields = {}; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -300,7 +296,7 @@ class Struct$Type extends MessageType { options: BinaryWriteOptions, ): IBinaryWriter { /* map fields = 1; */ - for (let k of Object.keys(message.fields)) { + for (let k of globalThis.Object.keys(message.fields)) { writer .tag(1, WireType.LengthDelimited) .fork() @@ -385,7 +381,10 @@ class Value$Type extends MessageType { case 'nullValue': return null; case 'numberValue': - return message.kind.numberValue; + let numberValue = message.kind.numberValue; + if (typeof numberValue == 'number' && !Number.isFinite(numberValue)) + throw new globalThis.Error(); + return numberValue; case 'stringValue': return message.kind.stringValue; case 'listValue': @@ -446,11 +445,8 @@ class Value$Type extends MessageType { return target; } create(value?: PartialMessage): Value { - const message = { kind: { oneofKind: undefined } }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.kind = { oneofKind: undefined }; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -616,11 +612,8 @@ class ListValue$Type extends MessageType { return target; } create(value?: PartialMessage): ListValue { - const message = { values: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.values = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; diff --git a/packages/client/src/gen/google/protobuf/timestamp.ts b/packages/client/src/gen/google/protobuf/timestamp.ts index 7274be52b4..853e7bd9b4 100644 --- a/packages/client/src/gen/google/protobuf/timestamp.ts +++ b/packages/client/src/gen/google/protobuf/timestamp.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// @generated by protobuf-ts 2.9.0 with parameter long_type_string,client_generic,server_none,eslint_disable +// @generated by protobuf-ts 2.9.3 with parameter long_type_string,client_generic,server_none,eslint_disable // @generated from protobuf file "google/protobuf/timestamp.proto" (package "google.protobuf", syntax proto3) // tslint:disable // @@ -44,7 +44,6 @@ import type { PartialMessage, } from '@protobuf-ts/runtime'; import { - MESSAGE_TYPE, MessageType, PbLong, reflectionMergePartial, @@ -285,11 +284,9 @@ class Timestamp$Type extends MessageType { return target; } create(value?: PartialMessage): Timestamp { - const message = { seconds: '0', nanos: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.seconds = '0'; + message.nanos = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; diff --git a/packages/client/src/gen/video/sfu/event/events.ts b/packages/client/src/gen/video/sfu/event/events.ts index e1e2dac5b3..12af85c2d5 100644 --- a/packages/client/src/gen/video/sfu/event/events.ts +++ b/packages/client/src/gen/video/sfu/event/events.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// @generated by protobuf-ts 2.9.0 with parameter long_type_string,client_generic,server_none,eslint_disable +// @generated by protobuf-ts 2.9.3 with parameter long_type_string,client_generic,server_none,eslint_disable // @generated from protobuf file "video/sfu/event/events.proto" (package "stream.video.sfu.event", syntax proto3) // tslint:disable import type { @@ -10,7 +10,6 @@ import type { PartialMessage, } from '@protobuf-ts/runtime'; import { - MESSAGE_TYPE, MessageType, reflectionMergePartial, UnknownFieldHandler, @@ -884,11 +883,8 @@ class SfuEvent$Type extends MessageType { ]); } create(value?: PartialMessage): SfuEvent { - const message = { eventPayload: { oneofKind: undefined } }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.eventPayload = { oneofKind: undefined }; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1281,11 +1277,8 @@ class PinsChanged$Type extends MessageType { ]); } create(value?: PartialMessage): PinsChanged { - const message = { pins: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.pins = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1359,11 +1352,7 @@ class Error$Type extends MessageType { ]); } create(value?: PartialMessage): Error { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1451,11 +1440,9 @@ class ICETrickle$Type extends MessageType { ]); } create(value?: PartialMessage): ICETrickle { - const message = { peerType: 0, iceCandidate: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.peerType = 0; + message.iceCandidate = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1534,11 +1521,8 @@ class ICERestart$Type extends MessageType { ]); } create(value?: PartialMessage): ICERestart { - const message = { peerType: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.peerType = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1619,11 +1603,8 @@ class SfuRequest$Type extends MessageType { ]); } create(value?: PartialMessage): SfuRequest { - const message = { requestPayload: { oneofKind: undefined } }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.requestPayload = { oneofKind: undefined }; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1719,11 +1700,7 @@ class HealthCheckRequest$Type extends MessageType { super('stream.video.sfu.event.HealthCheckRequest', []); } create(value?: PartialMessage): HealthCheckRequest { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1768,11 +1745,7 @@ class HealthCheckResponse$Type extends MessageType { ]); } create(value?: PartialMessage): HealthCheckResponse { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1861,11 +1834,10 @@ class TrackPublished$Type extends MessageType { ]); } create(value?: PartialMessage): TrackPublished { - const message = { userId: '', sessionId: '', type: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; + message.type = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1981,11 +1953,11 @@ class TrackUnpublished$Type extends MessageType { ]); } create(value?: PartialMessage): TrackUnpublished { - const message = { userId: '', sessionId: '', type: 0, cause: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; + message.type = 0; + message.cause = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2105,16 +2077,11 @@ class JoinRequest$Type extends MessageType { ]); } create(value?: PartialMessage): JoinRequest { - const message = { - token: '', - sessionId: '', - subscriberSdp: '', - fastReconnect: false, - }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.token = ''; + message.sessionId = ''; + message.subscriberSdp = ''; + message.fastReconnect = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2249,11 +2216,10 @@ class Migration$Type extends MessageType { ]); } create(value?: PartialMessage): Migration { - const message = { fromSfuId: '', announcedTracks: [], subscriptions: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.fromSfuId = ''; + message.announcedTracks = []; + message.subscriptions = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2350,11 +2316,8 @@ class JoinResponse$Type extends MessageType { ]); } create(value?: PartialMessage): JoinResponse { - const message = { reconnected: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.reconnected = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2438,11 +2401,8 @@ class ParticipantJoined$Type extends MessageType { ]); } create(value?: PartialMessage): ParticipantJoined { - const message = { callCid: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.callCid = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2526,11 +2486,8 @@ class ParticipantLeft$Type extends MessageType { ]); } create(value?: PartialMessage): ParticipantLeft { - const message = { callCid: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.callCid = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2614,11 +2571,9 @@ class SubscriberOffer$Type extends MessageType { ]); } create(value?: PartialMessage): SubscriberOffer { - const message = { iceRestart: false, sdp: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.iceRestart = false; + message.sdp = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2692,11 +2647,8 @@ class PublisherAnswer$Type extends MessageType { ]); } create(value?: PartialMessage): PublisherAnswer { - const message = { sdp: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.sdp = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2772,11 +2724,8 @@ class ConnectionQualityChanged$Type extends MessageType, ): ConnectionQualityChanged { - const message = { connectionQualityUpdates: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.connectionQualityUpdates = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2865,11 +2814,10 @@ class ConnectionQualityInfo$Type extends MessageType { ]); } create(value?: PartialMessage): ConnectionQualityInfo { - const message = { userId: '', sessionId: '', connectionQuality: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; + message.connectionQuality = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2952,11 +2900,9 @@ class DominantSpeakerChanged$Type extends MessageType { create( value?: PartialMessage, ): DominantSpeakerChanged { - const message = { userId: '', sessionId: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3033,11 +2979,11 @@ class AudioLevel$Type extends MessageType { ]); } create(value?: PartialMessage): AudioLevel { - const message = { userId: '', sessionId: '', level: 0, isSpeaking: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; + message.level = 0; + message.isSpeaking = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3128,11 +3074,8 @@ class AudioLevelChanged$Type extends MessageType { ]); } create(value?: PartialMessage): AudioLevelChanged { - const message = { audioLevels: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.audioLevels = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3211,11 +3154,8 @@ class AudioMediaRequest$Type extends MessageType { ]); } create(value?: PartialMessage): AudioMediaRequest { - const message = { channelCount: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.channelCount = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3289,11 +3229,7 @@ class AudioSender$Type extends MessageType { ]); } create(value?: PartialMessage): AudioSender { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3397,11 +3333,10 @@ class VideoMediaRequest$Type extends MessageType { ]); } create(value?: PartialMessage): VideoMediaRequest { - const message = { idealHeight: 0, idealWidth: 0, idealFrameRate: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.idealHeight = 0; + message.idealWidth = 0; + message.idealFrameRate = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3506,18 +3441,13 @@ class VideoLayerSetting$Type extends MessageType { ]); } create(value?: PartialMessage): VideoLayerSetting { - const message = { - name: '', - active: false, - maxBitrate: 0, - scaleResolutionDownBy: 0, - priority: 0, - maxFramerate: 0, - }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.name = ''; + message.active = false; + message.maxBitrate = 0; + message.scaleResolutionDownBy = 0; + message.priority = 0; + message.maxFramerate = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3643,11 +3573,8 @@ class VideoSender$Type extends MessageType { ]); } create(value?: PartialMessage): VideoSender { - const message = { layers: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.layers = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3768,11 +3695,9 @@ class ChangePublishQuality$Type extends MessageType { ]); } create(value?: PartialMessage): ChangePublishQuality { - const message = { audioSenders: [], videoSenders: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.audioSenders = []; + message.videoSenders = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3859,11 +3784,8 @@ class CallGrantsUpdated$Type extends MessageType { ]); } create(value?: PartialMessage): CallGrantsUpdated { - const message = { message: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.message = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -3955,11 +3877,8 @@ class GoAway$Type extends MessageType { ]); } create(value?: PartialMessage): GoAway { - const message = { reason: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.reason = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; diff --git a/packages/client/src/gen/video/sfu/models/models.ts b/packages/client/src/gen/video/sfu/models/models.ts index 069afafb8e..b6ed5e976f 100644 --- a/packages/client/src/gen/video/sfu/models/models.ts +++ b/packages/client/src/gen/video/sfu/models/models.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// @generated by protobuf-ts 2.9.0 with parameter long_type_string,client_generic,server_none,eslint_disable +// @generated by protobuf-ts 2.9.3 with parameter long_type_string,client_generic,server_none,eslint_disable // @generated from protobuf file "video/sfu/models/models.proto" (package "stream.video.sfu.models", syntax proto3) // tslint:disable import type { @@ -10,7 +10,6 @@ import type { PartialMessage, } from '@protobuf-ts/runtime'; import { - MESSAGE_TYPE, MessageType, reflectionMergePartial, UnknownFieldHandler, @@ -737,11 +736,9 @@ class CallState$Type extends MessageType { ]); } create(value?: PartialMessage): CallState { - const message = { participants: [], pins: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.participants = []; + message.pins = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -858,11 +855,9 @@ class ParticipantCount$Type extends MessageType { ]); } create(value?: PartialMessage): ParticipantCount { - const message = { total: 0, anonymous: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.total = 0; + message.anonymous = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -937,11 +932,9 @@ class Pin$Type extends MessageType { ]); } create(value?: PartialMessage): Pin { - const message = { userId: '', sessionId: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; } @@ -1061,23 +1054,18 @@ class Participant$Type extends MessageType { ]); } create(value?: PartialMessage): Participant { - const message = { - userId: '', - sessionId: '', - publishedTracks: [], - trackLookupPrefix: '', - connectionQuality: 0, - isSpeaking: false, - isDominantSpeaker: false, - audioLevel: 0, - name: '', - image: '', - roles: [], - }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; + message.publishedTracks = []; + message.trackLookupPrefix = ''; + message.connectionQuality = 0; + message.isSpeaking = false; + message.isDominantSpeaker = false; + message.audioLevel = 0; + message.name = ''; + message.image = ''; + message.roles = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1252,11 +1240,9 @@ class StreamQuality$Type extends MessageType { ]); } create(value?: PartialMessage): StreamQuality { - const message = { videoQuality: 0, userId: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.videoQuality = 0; + message.userId = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1331,11 +1317,9 @@ class VideoDimension$Type extends MessageType { ]); } create(value?: PartialMessage): VideoDimension { - const message = { width: 0, height: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.width = 0; + message.height = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1427,11 +1411,11 @@ class VideoLayer$Type extends MessageType { ]); } create(value?: PartialMessage): VideoLayer { - const message = { rid: '', bitrate: 0, fps: 0, quality: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.rid = ''; + message.bitrate = 0; + message.fps = 0; + message.quality = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1557,18 +1541,13 @@ class Codec$Type extends MessageType { ]); } create(value?: PartialMessage): Codec { - const message = { - payloadType: 0, - name: '', - fmtpLine: '', - clockRate: 0, - encodingParameters: '', - feedbacks: [], - }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.payloadType = 0; + message.name = ''; + message.fmtpLine = ''; + message.clockRate = 0; + message.encodingParameters = ''; + message.feedbacks = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1680,11 +1659,10 @@ class ICETrickle$Type extends MessageType { ]); } create(value?: PartialMessage): ICETrickle { - const message = { peerType: 0, iceCandidate: '', sessionId: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.peerType = 0; + message.iceCandidate = ''; + message.sessionId = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1785,19 +1763,14 @@ class TrackInfo$Type extends MessageType { ]); } create(value?: PartialMessage): TrackInfo { - const message = { - trackId: '', - trackType: 0, - layers: [], - mid: '', - dtx: false, - stereo: false, - red: false, - }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.trackId = ''; + message.trackType = 0; + message.layers = []; + message.mid = ''; + message.dtx = false; + message.stereo = false; + message.red = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1921,11 +1894,11 @@ class Call$Type extends MessageType { ]); } create(value?: PartialMessage): Call { - const message = { type: '', id: '', createdByUserId: '', hostUserId: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.type = ''; + message.id = ''; + message.createdByUserId = ''; + message.hostUserId = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; } @@ -2066,11 +2039,10 @@ class Error$Type extends MessageType { ]); } create(value?: PartialMessage): Error { - const message = { code: 0, message: '', shouldRetry: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.code = 0; + message.message = ''; + message.shouldRetry = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2152,11 +2124,7 @@ class ClientDetails$Type extends MessageType { ]); } create(value?: PartialMessage): ClientDetails { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2286,11 +2254,11 @@ class Sdk$Type extends MessageType { ]); } create(value?: PartialMessage): Sdk { - const message = { type: 0, major: '', minor: '', patch: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.type = 0; + message.major = ''; + message.minor = ''; + message.patch = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; } @@ -2381,11 +2349,10 @@ class OS$Type extends MessageType { ]); } create(value?: PartialMessage): OS { - const message = { name: '', version: '', architecture: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.name = ''; + message.version = ''; + message.architecture = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; } @@ -2465,11 +2432,9 @@ class Browser$Type extends MessageType { ]); } create(value?: PartialMessage): Browser { - const message = { name: '', version: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.name = ''; + message.version = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2544,11 +2509,9 @@ class Device$Type extends MessageType { ]); } create(value?: PartialMessage): Device { - const message = { name: '', version: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.name = ''; + message.version = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -2639,15 +2602,10 @@ class CallGrants$Type extends MessageType { ]); } create(value?: PartialMessage): CallGrants { - const message = { - canPublishAudio: false, - canPublishVideo: false, - canScreenshare: false, - }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.canPublishAudio = false; + message.canPublishVideo = false; + message.canScreenshare = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; diff --git a/packages/client/src/gen/video/sfu/signal_rpc/signal.client.ts b/packages/client/src/gen/video/sfu/signal_rpc/signal.client.ts index 876228c597..f599bf27c0 100644 --- a/packages/client/src/gen/video/sfu/signal_rpc/signal.client.ts +++ b/packages/client/src/gen/video/sfu/signal_rpc/signal.client.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// @generated by protobuf-ts 2.9.0 with parameter long_type_string,client_generic,server_none,eslint_disable +// @generated by protobuf-ts 2.9.3 with parameter long_type_string,client_generic,server_none,eslint_disable // @generated from protobuf file "video/sfu/signal_rpc/signal.proto" (package "stream.video.sfu.signal", syntax proto3) // tslint:disable import type { diff --git a/packages/client/src/gen/video/sfu/signal_rpc/signal.ts b/packages/client/src/gen/video/sfu/signal_rpc/signal.ts index 5be6809103..ffc5511253 100644 --- a/packages/client/src/gen/video/sfu/signal_rpc/signal.ts +++ b/packages/client/src/gen/video/sfu/signal_rpc/signal.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// @generated by protobuf-ts 2.9.0 with parameter long_type_string,client_generic,server_none,eslint_disable +// @generated by protobuf-ts 2.9.3 with parameter long_type_string,client_generic,server_none,eslint_disable // @generated from protobuf file "video/sfu/signal_rpc/signal.proto" (package "stream.video.sfu.signal", syntax proto3) // tslint:disable import { @@ -19,7 +19,6 @@ import type { PartialMessage, } from '@protobuf-ts/runtime'; import { - MESSAGE_TYPE, MessageType, reflectionMergePartial, UnknownFieldHandler, @@ -235,11 +234,9 @@ class ICERestartRequest$Type extends MessageType { ]); } create(value?: PartialMessage): ICERestartRequest { - const message = { sessionId: '', peerType: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.sessionId = ''; + message.peerType = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -313,11 +310,7 @@ class ICERestartResponse$Type extends MessageType { ]); } create(value?: PartialMessage): ICERestartResponse { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -403,11 +396,9 @@ class UpdateMuteStatesRequest$Type extends MessageType create( value?: PartialMessage, ): UpdateMuteStatesRequest { - const message = { sessionId: '', muteStates: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.sessionId = ''; + message.muteStates = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -489,11 +480,7 @@ class UpdateMuteStatesResponse$Type extends MessageType, ): UpdateMuteStatesResponse { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -580,11 +567,9 @@ class TrackMuteState$Type extends MessageType { ]); } create(value?: PartialMessage): TrackMuteState { - const message = { trackType: 0, muted: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.trackType = 0; + message.muted = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -658,11 +643,8 @@ class AudioMuteChanged$Type extends MessageType { ]); } create(value?: PartialMessage): AudioMuteChanged { - const message = { muted: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.muted = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -730,11 +712,8 @@ class VideoMuteChanged$Type extends MessageType { ]); } create(value?: PartialMessage): VideoMuteChanged { - const message = { muted: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.muted = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -811,11 +790,9 @@ class UpdateSubscriptionsRequest$Type extends MessageType, ): UpdateSubscriptionsRequest { - const message = { sessionId: '', tracks: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.sessionId = ''; + message.tracks = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -901,11 +878,7 @@ class UpdateSubscriptionsResponse$Type extends MessageType, ): UpdateSubscriptionsResponse { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -997,11 +970,10 @@ class TrackSubscriptionDetails$Type extends MessageType, ): TrackSubscriptionDetails { - const message = { userId: '', sessionId: '', trackType: 0 }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.userId = ''; + message.sessionId = ''; + message.trackType = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1103,11 +1075,10 @@ class SendAnswerRequest$Type extends MessageType { ]); } create(value?: PartialMessage): SendAnswerRequest { - const message = { peerType: 0, sdp: '', sessionId: '' }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.peerType = 0; + message.sdp = ''; + message.sessionId = ''; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1187,11 +1158,7 @@ class SendAnswerResponse$Type extends MessageType { ]); } create(value?: PartialMessage): SendAnswerResponse { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1268,11 +1235,7 @@ class ICETrickleResponse$Type extends MessageType { ]); } create(value?: PartialMessage): ICETrickleResponse { - const message = {}; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1357,11 +1320,10 @@ class SetPublisherRequest$Type extends MessageType { ]); } create(value?: PartialMessage): SetPublisherRequest { - const message = { sdp: '', sessionId: '', tracks: [] }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.sdp = ''; + message.sessionId = ''; + message.tracks = []; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1450,11 +1412,10 @@ class SetPublisherResponse$Type extends MessageType { ]); } create(value?: PartialMessage): SetPublisherResponse { - const message = { sdp: '', sessionId: '', iceRestart: false }; - globalThis.Object.defineProperty(message, MESSAGE_TYPE, { - enumerable: false, - value: this, - }); + const message = globalThis.Object.create(this.messagePrototype!); + message.sdp = ''; + message.sessionId = ''; + message.iceRestart = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; diff --git a/packages/react-bindings/src/contexts/StreamI18nContext.tsx b/packages/react-bindings/src/contexts/StreamI18nContext.tsx index 3fa0280005..0d3519c8e6 100644 --- a/packages/react-bindings/src/contexts/StreamI18nContext.tsx +++ b/packages/react-bindings/src/contexts/StreamI18nContext.tsx @@ -23,6 +23,7 @@ const StreamI18nContext = createContext({ type CreateI18nParams = { i18nInstance?: StreamI18n; language?: string; + fallbackLanguage?: string; translationsOverrides?: TranslationsMap; }; @@ -44,12 +45,17 @@ export const StreamI18nProvider = ({ export const useCreateI18n = ({ i18nInstance, language, + fallbackLanguage, translationsOverrides, }: CreateI18nParams) => { const [i18n] = useState( () => i18nInstance || - new StreamI18n({ currentLanguage: language, translationsOverrides }), + new StreamI18n({ + currentLanguage: language, + fallbackLanguage, + translationsOverrides, + }), ); const [t, setTranslationFn] = useState( () => defaultTranslationFunction, diff --git a/packages/react-bindings/src/i18n/StreamI18n.ts b/packages/react-bindings/src/i18n/StreamI18n.ts index 4be09bcea2..aa24b4ed37 100644 --- a/packages/react-bindings/src/i18n/StreamI18n.ts +++ b/packages/react-bindings/src/i18n/StreamI18n.ts @@ -12,7 +12,8 @@ export const DEFAULT_NAMESPACE = 'stream-video'; const DEFAULT_CONFIG = { debug: false, currentLanguage: DEFAULT_LANGUAGE, -}; + fallbackLanguage: false, +} as const; const DEFAULT_TRANSLATIONS_REGISTRY = mapToRegistry({}, DEFAULT_NAMESPACE); @@ -21,6 +22,8 @@ export const defaultTranslationFunction = (key: string) => key; export type StreamI18nConstructor = { /** Language into which the provided strings are translated */ currentLanguage?: TranslationLanguage; + /** Fallback language which will be used if no translation is found for current language */ + fallbackLanguage?: TranslationLanguage; /** Logs info level to console output. Helps find issues with loading not working. */ debug?: boolean; /** Custom translations that will be merged with the defaults provided by the library. */ @@ -37,13 +40,14 @@ export class StreamI18n { const { debug = DEFAULT_CONFIG.debug, currentLanguage = DEFAULT_CONFIG.currentLanguage, + fallbackLanguage = DEFAULT_CONFIG.fallbackLanguage, translationsOverrides, } = options; this.i18nInstance = i18next.createInstance({ debug, defaultNS: DEFAULT_NAMESPACE, - fallbackLng: false, + fallbackLng: fallbackLanguage, interpolation: { escapeValue: false }, keySeparator: false, lng: currentLanguage, diff --git a/packages/react-sdk/CHANGELOG.md b/packages/react-sdk/CHANGELOG.md index dd882be54a..c671bce18c 100644 --- a/packages/react-sdk/CHANGELOG.md +++ b/packages/react-sdk/CHANGELOG.md @@ -2,782 +2,898 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +# Styling + +- New global variables like spacing, font-size, colors, button states etc, have been added. The old variables are still available. +- Major style changes have been made to the following items: Callstats, Device settings, Buttons, Particpant layout +- Icons have been updated and added + +# Components + +## Button + +### CompositeButton.tsx + +- `ToggleMenuButton` prop has been added by default `DefaultToggleMenuButton` +- The composite buttons has an optional variant `primary` and `secondary` to indicate a primary and secondary active state of the button +- An `active` class has been added to the `DefaultToggleMenuButton`. + +## Call controls + +- The default caption value has been removed +- A title has been added to the buttons +- Some buttons have been provided a variant prop +- CallStats button has been replaced with Reactions button + +### CancelCallButton.tsx + +- A cancel call confirmation button is added to allow the user end the call for all participants or leave the call. + +### ToggleAudioButton.tsx + +- A default permission state has been added to indicate that the browsers audio permissions have not been granted + +### ToggleVideoButton.tsx + +- A default permission state has been added to indicate that the browsers camera permissions have not been granted + +### ReactionsButton.tsx + +- Added a layout prop `horizontal` or `vertical` defaults to `horizontal` + +### RecordCallButton.tsx + +- An end recording confirmation button component has been added. To display a confirmation modal to the user either continue the recording or cancel it + +### Other + +### CallParticipantsList.tsx + +- Removed the copy url button invite new participants +- Removed other related invite functionality + +### CallRecordingListHeader + +- Put the refresh functionality to optional + +### CallStats.tsx + +- The stats are defined with a `lowBound` (default 75) and a `highBound` (default 400) prop to indicate what you think is an acceptable latency. + +### DropdownSelect.tsx + +- Added a dropdown selector used in the device settings + +### Icon.tsx + +- Added a className prop to icon + +### MenuToggle.tsx + +- Ability to choose between to visual types `portal` or `menu` to display the MenuToggle. + +### Notification.tsx + +- Added a `close` prop so a notification can be closed by the user + +### RecordingInProgressNotification.tsx + +- Added a notification that can be used to be displayed when a recording is in progress + +### DefaultVideoPlaceholder + +- Added an initials fallback + ### [0.4.26](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.25...@stream-io/video-react-sdk-0.4.26) (2024-02-19) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.11` -* `@stream-io/video-react-bindings` updated to version `0.3.22` +- `@stream-io/video-client` updated to version `0.5.11` +- `@stream-io/video-react-bindings` updated to version `0.3.22` + ### [0.4.25](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.24...@stream-io/video-react-sdk-0.4.25) (2024-02-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.10` -* `@stream-io/video-react-bindings` updated to version `0.3.21` +- `@stream-io/video-client` updated to version `0.5.10` +- `@stream-io/video-react-bindings` updated to version `0.3.21` + ### [0.4.24](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.23...@stream-io/video-react-sdk-0.4.24) (2024-02-12) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.9` -* `@stream-io/video-react-bindings` updated to version `0.3.20` +- `@stream-io/video-client` updated to version `0.5.9` +- `@stream-io/video-react-bindings` updated to version `0.3.20` + ### [0.4.23](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.22...@stream-io/video-react-sdk-0.4.23) (2024-02-06) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.8` -* `@stream-io/video-react-bindings` updated to version `0.3.19` +- `@stream-io/video-client` updated to version `0.5.8` +- `@stream-io/video-react-bindings` updated to version `0.3.19` + ### [0.4.22](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.21...@stream-io/video-react-sdk-0.4.22) (2024-01-29) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.7` -* `@stream-io/video-react-bindings` updated to version `0.3.18` +- `@stream-io/video-client` updated to version `0.5.7` +- `@stream-io/video-react-bindings` updated to version `0.3.18` + ### [0.4.21](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.20...@stream-io/video-react-sdk-0.4.21) (2024-01-19) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.6` -* `@stream-io/video-react-bindings` updated to version `0.3.17` -### [0.4.20](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.19...@stream-io/video-react-sdk-0.4.20) (2024-01-16) +- `@stream-io/video-client` updated to version `0.5.6` +- `@stream-io/video-react-bindings` updated to version `0.3.17` +### [0.4.20](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.19...@stream-io/video-react-sdk-0.4.20) (2024-01-16) ### Bug Fixes -* **react-sdk:** handle external full-screen toggling ([#1243](https://github.com/GetStream/stream-video-js/issues/1243)) ([9578155](https://github.com/GetStream/stream-video-js/commit/95781555e8450c780ca73cf9d9d940d12613d893)) +- **react-sdk:** handle external full-screen toggling ([#1243](https://github.com/GetStream/stream-video-js/issues/1243)) ([9578155](https://github.com/GetStream/stream-video-js/commit/95781555e8450c780ca73cf9d9d940d12613d893)) ### [0.4.19](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.18...@stream-io/video-react-sdk-0.4.19) (2024-01-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.5` -* `@stream-io/video-react-bindings` updated to version `0.3.16` +- `@stream-io/video-client` updated to version `0.5.5` +- `@stream-io/video-react-bindings` updated to version `0.3.16` + ### [0.4.18](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.17...@stream-io/video-react-sdk-0.4.18) (2024-01-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.4` -* `@stream-io/video-react-bindings` updated to version `0.3.15` +- `@stream-io/video-client` updated to version `0.5.4` +- `@stream-io/video-react-bindings` updated to version `0.3.15` + ### [0.4.17](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.16...@stream-io/video-react-sdk-0.4.17) (2023-12-22) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.3` -* `@stream-io/video-react-bindings` updated to version `0.3.14` +- `@stream-io/video-client` updated to version `0.5.3` +- `@stream-io/video-react-bindings` updated to version `0.3.14` + ### [0.4.16](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.15...@stream-io/video-react-sdk-0.4.16) (2023-12-11) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.2` -* `@stream-io/video-react-bindings` updated to version `0.3.13` +- `@stream-io/video-client` updated to version `0.5.2` +- `@stream-io/video-react-bindings` updated to version `0.3.13` + ### [0.4.15](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.14...@stream-io/video-react-sdk-0.4.15) (2023-12-05) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.1` -* `@stream-io/video-react-bindings` updated to version `0.3.12` +- `@stream-io/video-client` updated to version `0.5.1` +- `@stream-io/video-react-bindings` updated to version `0.3.12` + ### [0.4.14](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.13...@stream-io/video-react-sdk-0.4.14) (2023-11-29) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.5.0` -* `@stream-io/video-react-bindings` updated to version `0.3.11` +- `@stream-io/video-client` updated to version `0.5.0` +- `@stream-io/video-react-bindings` updated to version `0.3.11` + ### [0.4.13](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.12...@stream-io/video-react-sdk-0.4.13) (2023-11-27) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.10` -* `@stream-io/video-react-bindings` updated to version `0.3.10` +- `@stream-io/video-client` updated to version `0.4.10` +- `@stream-io/video-react-bindings` updated to version `0.3.10` + ### [0.4.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.11...@stream-io/video-react-sdk-0.4.12) (2023-11-22) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.9` -* `@stream-io/video-react-bindings` updated to version `0.3.9` +- `@stream-io/video-client` updated to version `0.4.9` +- `@stream-io/video-react-bindings` updated to version `0.3.9` ### Features -* **participant-view:** allow opting-out from rendering VideoPlaceholder ([#1198](https://github.com/GetStream/stream-video-js/issues/1198)) ([acb020c](https://github.com/GetStream/stream-video-js/commit/acb020c8157a1338771bef11ef5e501bc9cd6f69)) +- **participant-view:** allow opting-out from rendering VideoPlaceholder ([#1198](https://github.com/GetStream/stream-video-js/issues/1198)) ([acb020c](https://github.com/GetStream/stream-video-js/commit/acb020c8157a1338771bef11ef5e501bc9cd6f69)) ### [0.4.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.10...@stream-io/video-react-sdk-0.4.11) (2023-11-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.8` -* `@stream-io/video-react-bindings` updated to version `0.3.8` +- `@stream-io/video-client` updated to version `0.4.8` +- `@stream-io/video-react-bindings` updated to version `0.3.8` + ### [0.4.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.9...@stream-io/video-react-sdk-0.4.10) (2023-11-13) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.7` -* `@stream-io/video-react-bindings` updated to version `0.3.7` +- `@stream-io/video-client` updated to version `0.4.7` +- `@stream-io/video-react-bindings` updated to version `0.3.7` ### Features -* **device-api:** Browser Permissions API ([#1184](https://github.com/GetStream/stream-video-js/issues/1184)) ([a0b3573](https://github.com/GetStream/stream-video-js/commit/a0b3573b630ff8450953cdf1102fe722aea83f6f)) +- **device-api:** Browser Permissions API ([#1184](https://github.com/GetStream/stream-video-js/issues/1184)) ([a0b3573](https://github.com/GetStream/stream-video-js/commit/a0b3573b630ff8450953cdf1102fe722aea83f6f)) ### [0.4.9](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.8...@stream-io/video-react-sdk-0.4.9) (2023-11-13) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.6` -* `@stream-io/video-react-bindings` updated to version `0.3.6` +- `@stream-io/video-client` updated to version `0.4.6` +- `@stream-io/video-react-bindings` updated to version `0.3.6` + ### [0.4.8](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.7...@stream-io/video-react-sdk-0.4.8) (2023-11-07) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.5` -* `@stream-io/video-react-bindings` updated to version `0.3.5` -* `@stream-io/video-styling` updated to version `0.1.14` +- `@stream-io/video-client` updated to version `0.4.5` +- `@stream-io/video-react-bindings` updated to version `0.3.5` +- `@stream-io/video-styling` updated to version `0.1.14` ### Bug Fixes -* lift the debug helpers from the SDK to Pronto ([#1182](https://github.com/GetStream/stream-video-js/issues/1182)) ([8f31efc](https://github.com/GetStream/stream-video-js/commit/8f31efc71d9f85ef147d21b42f23876599c36072)) +- lift the debug helpers from the SDK to Pronto ([#1182](https://github.com/GetStream/stream-video-js/issues/1182)) ([8f31efc](https://github.com/GetStream/stream-video-js/commit/8f31efc71d9f85ef147d21b42f23876599c36072)) ### [0.4.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.6...@stream-io/video-react-sdk-0.4.7) (2023-11-03) - ### Bug Fixes -* set `key` prop to the correct element ([#1178](https://github.com/GetStream/stream-video-js/issues/1178)) ([b24c07d](https://github.com/GetStream/stream-video-js/commit/b24c07dd366e8aa64055aae7dd48cabe8761eac0)), closes [#1176](https://github.com/GetStream/stream-video-js/issues/1176) +- set `key` prop to the correct element ([#1178](https://github.com/GetStream/stream-video-js/issues/1178)) ([b24c07d](https://github.com/GetStream/stream-video-js/commit/b24c07dd366e8aa64055aae7dd48cabe8761eac0)), closes [#1176](https://github.com/GetStream/stream-video-js/issues/1176) ### [0.4.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.5...@stream-io/video-react-sdk-0.4.6) (2023-11-02) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.4` -* `@stream-io/video-react-bindings` updated to version `0.3.4` +- `@stream-io/video-client` updated to version `0.4.4` +- `@stream-io/video-react-bindings` updated to version `0.3.4` ### Bug Fixes -* allow audio and screen share audio tracks, delay setSinkId ([#1176](https://github.com/GetStream/stream-video-js/issues/1176)) ([6a099c5](https://github.com/GetStream/stream-video-js/commit/6a099c5c7cc6f5d389961a7c594e914e19be4ddb)) +- allow audio and screen share audio tracks, delay setSinkId ([#1176](https://github.com/GetStream/stream-video-js/issues/1176)) ([6a099c5](https://github.com/GetStream/stream-video-js/commit/6a099c5c7cc6f5d389961a7c594e914e19be4ddb)) ### [0.4.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.4...@stream-io/video-react-sdk-0.4.5) (2023-11-01) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.3` -* `@stream-io/video-react-bindings` updated to version `0.3.3` +- `@stream-io/video-client` updated to version `0.4.3` +- `@stream-io/video-react-bindings` updated to version `0.3.3` + ### [0.4.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.3...@stream-io/video-react-sdk-0.4.4) (2023-11-01) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.2` -* `@stream-io/video-react-bindings` updated to version `0.3.2` +- `@stream-io/video-client` updated to version `0.4.2` +- `@stream-io/video-react-bindings` updated to version `0.3.2` ### Bug Fixes -* respect server-side settings in the lobby ([#1175](https://github.com/GetStream/stream-video-js/issues/1175)) ([b722a0a](https://github.com/GetStream/stream-video-js/commit/b722a0a4f8fd4e4e56787db3d9a56e45ee195974)) +- respect server-side settings in the lobby ([#1175](https://github.com/GetStream/stream-video-js/issues/1175)) ([b722a0a](https://github.com/GetStream/stream-video-js/commit/b722a0a4f8fd4e4e56787db3d9a56e45ee195974)) ### [0.4.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.2...@stream-io/video-react-sdk-0.4.3) (2023-10-30) - ### Bug Fixes -* add marker classes for the default `VideoPreview` components ([#1172](https://github.com/GetStream/stream-video-js/issues/1172)) ([7948cd8](https://github.com/GetStream/stream-video-js/commit/7948cd81a5ad6271872239a77b2a5ab8a856d231)) +- add marker classes for the default `VideoPreview` components ([#1172](https://github.com/GetStream/stream-video-js/issues/1172)) ([7948cd8](https://github.com/GetStream/stream-video-js/commit/7948cd81a5ad6271872239a77b2a5ab8a856d231)) ### [0.4.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.1...@stream-io/video-react-sdk-0.4.2) (2023-10-30) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.1` -* `@stream-io/video-react-bindings` updated to version `0.3.1` -### [0.4.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.0...@stream-io/video-react-sdk-0.4.1) (2023-10-27) +- `@stream-io/video-client` updated to version `0.4.1` +- `@stream-io/video-react-bindings` updated to version `0.3.1` +### [0.4.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.4.0...@stream-io/video-react-sdk-0.4.1) (2023-10-27) ### Bug Fixes -* **video-preview:** accept `className` prop ([#1166](https://github.com/GetStream/stream-video-js/issues/1166)) ([bfbfa1e](https://github.com/GetStream/stream-video-js/commit/bfbfa1ed52d4a0b19f9221252640d2926ebda641)) +- **video-preview:** accept `className` prop ([#1166](https://github.com/GetStream/stream-video-js/issues/1166)) ([bfbfa1e](https://github.com/GetStream/stream-video-js/commit/bfbfa1ed52d4a0b19f9221252640d2926ebda641)) ## [0.4.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.47...@stream-io/video-react-sdk-0.4.0) (2023-10-27) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.4.0` -* `@stream-io/video-react-bindings` updated to version `0.3.0` +- `@stream-io/video-client` updated to version `0.4.0` +- `@stream-io/video-react-bindings` updated to version `0.3.0` ### ⚠ BREAKING CHANGES -* **react-sdk:** Universal Device Management API (#1127) +- **react-sdk:** Universal Device Management API (#1127) ### Features -* **react-sdk:** Universal Device Management API ([#1127](https://github.com/GetStream/stream-video-js/issues/1127)) ([aeb3561](https://github.com/GetStream/stream-video-js/commit/aeb35612745f45254b536281c5f81d1bcac2bab5)) +- **react-sdk:** Universal Device Management API ([#1127](https://github.com/GetStream/stream-video-js/issues/1127)) ([aeb3561](https://github.com/GetStream/stream-video-js/commit/aeb35612745f45254b536281c5f81d1bcac2bab5)) ### [0.3.47](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.46...@stream-io/video-react-sdk-0.3.47) (2023-10-26) - ### Bug Fixes -* Localize "Screen Share" caption ([#1164](https://github.com/GetStream/stream-video-js/issues/1164)) ([0a9ed96](https://github.com/GetStream/stream-video-js/commit/0a9ed960ee5ef8409b61dc5d747912b17a521160)) +- Localize "Screen Share" caption ([#1164](https://github.com/GetStream/stream-video-js/issues/1164)) ([0a9ed96](https://github.com/GetStream/stream-video-js/commit/0a9ed960ee5ef8409b61dc5d747912b17a521160)) ### [0.3.46](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.45...@stream-io/video-react-sdk-0.3.46) (2023-10-25) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.36` -* `@stream-io/video-react-bindings` updated to version `0.2.37` -### [0.3.45](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.44...@stream-io/video-react-sdk-0.3.45) (2023-10-24) +- `@stream-io/video-client` updated to version `0.3.36` +- `@stream-io/video-react-bindings` updated to version `0.2.37` +### [0.3.45](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.44...@stream-io/video-react-sdk-0.3.45) (2023-10-24) ### Bug Fixes -* add missing translations ([#1158](https://github.com/GetStream/stream-video-js/issues/1158)) ([6eb0c7a](https://github.com/GetStream/stream-video-js/commit/6eb0c7abf1b6a403438e4d80f275265e07e4f82f)) +- add missing translations ([#1158](https://github.com/GetStream/stream-video-js/issues/1158)) ([6eb0c7a](https://github.com/GetStream/stream-video-js/commit/6eb0c7abf1b6a403438e4d80f275265e07e4f82f)) ### [0.3.44](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.43...@stream-io/video-react-sdk-0.3.44) (2023-10-19) - ### Bug Fixes -* sync video "paused" state more accurately ([#1150](https://github.com/GetStream/stream-video-js/issues/1150)) ([39cd42f](https://github.com/GetStream/stream-video-js/commit/39cd42f0035bbabdd9bb078fc8df9192f3b6c42f)) +- sync video "paused" state more accurately ([#1150](https://github.com/GetStream/stream-video-js/issues/1150)) ([39cd42f](https://github.com/GetStream/stream-video-js/commit/39cd42f0035bbabdd9bb078fc8df9192f3b6c42f)) ### [0.3.43](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.42...@stream-io/video-react-sdk-0.3.43) (2023-10-19) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.35` -* `@stream-io/video-react-bindings` updated to version `0.2.36` +- `@stream-io/video-client` updated to version `0.3.35` +- `@stream-io/video-react-bindings` updated to version `0.2.36` ### Features -* mute screenshare_audio, update to the newest OpenAPI schema ([#1148](https://github.com/GetStream/stream-video-js/issues/1148)) ([81c45a7](https://github.com/GetStream/stream-video-js/commit/81c45a77e6a526de05ce5457357d212fb3e613d9)) +- mute screenshare_audio, update to the newest OpenAPI schema ([#1148](https://github.com/GetStream/stream-video-js/issues/1148)) ([81c45a7](https://github.com/GetStream/stream-video-js/commit/81c45a77e6a526de05ce5457357d212fb3e613d9)) ### [0.3.42](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.41...@stream-io/video-react-sdk-0.3.42) (2023-10-18) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.34` -* `@stream-io/video-react-bindings` updated to version `0.2.35` -* `@stream-io/video-styling` updated to version `0.1.13` +- `@stream-io/video-client` updated to version `0.3.34` +- `@stream-io/video-react-bindings` updated to version `0.2.35` +- `@stream-io/video-styling` updated to version `0.1.13` ### Features -* **build:** ESM and CJS bundles ([#1144](https://github.com/GetStream/stream-video-js/issues/1144)) ([58b60ee](https://github.com/GetStream/stream-video-js/commit/58b60eee4b1cd667d2eef8f17ed4e6da74876a51)), closes [#1025](https://github.com/GetStream/stream-video-js/issues/1025) +- **build:** ESM and CJS bundles ([#1144](https://github.com/GetStream/stream-video-js/issues/1144)) ([58b60ee](https://github.com/GetStream/stream-video-js/commit/58b60eee4b1cd667d2eef8f17ed4e6da74876a51)), closes [#1025](https://github.com/GetStream/stream-video-js/issues/1025) ### [0.3.41](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.40...@stream-io/video-react-sdk-0.3.41) (2023-10-13) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.33` -* `@stream-io/video-react-bindings` updated to version `0.2.34` +- `@stream-io/video-client` updated to version `0.3.33` +- `@stream-io/video-react-bindings` updated to version `0.2.34` + ### [0.3.40](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.39...@stream-io/video-react-sdk-0.3.40) (2023-10-13) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.32` -* `@stream-io/video-react-bindings` updated to version `0.2.33` +- `@stream-io/video-client` updated to version `0.3.32` +- `@stream-io/video-react-bindings` updated to version `0.2.33` + ### [0.3.39](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.38...@stream-io/video-react-sdk-0.3.39) (2023-10-09) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.31` -* `@stream-io/video-react-bindings` updated to version `0.2.32` +- `@stream-io/video-client` updated to version `0.3.31` +- `@stream-io/video-react-bindings` updated to version `0.2.32` ### Bug Fixes -* sorting in paginated grid ([#1129](https://github.com/GetStream/stream-video-js/issues/1129)) ([d5b280a](https://github.com/GetStream/stream-video-js/commit/d5b280aadeaa4c718d0158561197c7045620ae0f)) +- sorting in paginated grid ([#1129](https://github.com/GetStream/stream-video-js/issues/1129)) ([d5b280a](https://github.com/GetStream/stream-video-js/commit/d5b280aadeaa4c718d0158561197c7045620ae0f)) ### [0.3.38](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.37...@stream-io/video-react-sdk-0.3.38) (2023-10-06) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.30` -* `@stream-io/video-react-bindings` updated to version `0.2.31` +- `@stream-io/video-client` updated to version `0.3.30` +- `@stream-io/video-react-bindings` updated to version `0.2.31` ### Features -* ScreenShare Audio support ([#1118](https://github.com/GetStream/stream-video-js/issues/1118)) ([5b63e1c](https://github.com/GetStream/stream-video-js/commit/5b63e1c5f52c76e3761e6907bd3786c19f0e5c6d)) +- ScreenShare Audio support ([#1118](https://github.com/GetStream/stream-video-js/issues/1118)) ([5b63e1c](https://github.com/GetStream/stream-video-js/commit/5b63e1c5f52c76e3761e6907bd3786c19f0e5c6d)) ### [0.3.37](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.36...@stream-io/video-react-sdk-0.3.37) (2023-10-05) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.29` -* `@stream-io/video-react-bindings` updated to version `0.2.30` -### [0.3.36](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.35...@stream-io/video-react-sdk-0.3.36) (2023-10-03) +- `@stream-io/video-client` updated to version `0.3.29` +- `@stream-io/video-react-bindings` updated to version `0.2.30` +### [0.3.36](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.35...@stream-io/video-react-sdk-0.3.36) (2023-10-03) ### Bug Fixes -* check if `currentParticipant` is actually initialized ([#1124](https://github.com/GetStream/stream-video-js/issues/1124)) ([797b84f](https://github.com/GetStream/stream-video-js/commit/797b84f9f63ae2c98a97b28afc08858705cd6840)) +- check if `currentParticipant` is actually initialized ([#1124](https://github.com/GetStream/stream-video-js/issues/1124)) ([797b84f](https://github.com/GetStream/stream-video-js/commit/797b84f9f63ae2c98a97b28afc08858705cd6840)) ### [0.3.35](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.34...@stream-io/video-react-sdk-0.3.35) (2023-10-02) - ### Bug Fixes -* requestPermission should be no-op when permission is already granted ([#1122](https://github.com/GetStream/stream-video-js/issues/1122)) ([f3d9e34](https://github.com/GetStream/stream-video-js/commit/f3d9e349825a6052850f7a78c3d6af9f517d136e)) +- requestPermission should be no-op when permission is already granted ([#1122](https://github.com/GetStream/stream-video-js/issues/1122)) ([f3d9e34](https://github.com/GetStream/stream-video-js/commit/f3d9e349825a6052850f7a78c3d6af9f517d136e)) ### [0.3.34](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.33...@stream-io/video-react-sdk-0.3.34) (2023-10-02) ### Dependency Updates -* `@stream-io/video-styling` updated to version `0.1.12` +- `@stream-io/video-styling` updated to version `0.1.12` ### Features -* **egress-composite:** add support for new options ([#1104](https://github.com/GetStream/stream-video-js/issues/1104)) ([2e039c2](https://github.com/GetStream/stream-video-js/commit/2e039c280cd808e6464ee3ab54e8c3606a0a0180)), closes [/github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx#L53-L117](https://github.com/GetStream//github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx/issues/L53-L117) +- **egress-composite:** add support for new options ([#1104](https://github.com/GetStream/stream-video-js/issues/1104)) ([2e039c2](https://github.com/GetStream/stream-video-js/commit/2e039c280cd808e6464ee3ab54e8c3606a0a0180)), closes [/github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx#L53-L117](https://github.com/GetStream//github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx/issues/L53-L117) ### [0.3.33](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.32...@stream-io/video-react-sdk-0.3.33) (2023-09-28) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.3.28` -* `@stream-io/video-react-bindings` updated to version `0.2.29` +- `@stream-io/video-client` updated to version `0.3.28` +- `@stream-io/video-react-bindings` updated to version `0.2.29` + ### [0.3.32](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.31...@stream-io/video-react-sdk-0.3.32) (2023-09-28) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.28` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.28` + ### [0.3.31](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.30...@stream-io/video-react-sdk-0.3.31) (2023-09-27) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.27` -* `@stream-io/video-styling` updated to version `0.1.11` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.27` +- `@stream-io/video-styling` updated to version `0.1.11` ### Features -* **Call Preview:** Support for call thumbnails ([#1099](https://github.com/GetStream/stream-video-js/issues/1099)) ([9274f76](https://github.com/GetStream/stream-video-js/commit/9274f760ed264ee0ee6ac97c6fe679288e067fd8)) +- **Call Preview:** Support for call thumbnails ([#1099](https://github.com/GetStream/stream-video-js/issues/1099)) ([9274f76](https://github.com/GetStream/stream-video-js/commit/9274f760ed264ee0ee6ac97c6fe679288e067fd8)) ### [0.3.30](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.29...@stream-io/video-react-sdk-0.3.30) (2023-09-27) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.26` -* `@stream-io/video-styling` updated to version `0.1.10` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.26` +- `@stream-io/video-styling` updated to version `0.1.10` ### Features -* **react-sdk:** LivestreamLayout ([#1103](https://github.com/GetStream/stream-video-js/issues/1103)) ([6636699](https://github.com/GetStream/stream-video-js/commit/6636699701dfd5eb5886c50781dd5f16a8470da5)) +- **react-sdk:** LivestreamLayout ([#1103](https://github.com/GetStream/stream-video-js/issues/1103)) ([6636699](https://github.com/GetStream/stream-video-js/commit/6636699701dfd5eb5886c50781dd5f16a8470da5)) ### [0.3.29](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.28...@stream-io/video-react-sdk-0.3.29) (2023-09-27) - ### Features -* **SpeakerLayout:** add participantsBarLimit ([#1090](https://github.com/GetStream/stream-video-js/issues/1090)) ([712f1e7](https://github.com/GetStream/stream-video-js/commit/712f1e7010fdb8859aaa6caba7e7d9e0f4557ccb)) +- **SpeakerLayout:** add participantsBarLimit ([#1090](https://github.com/GetStream/stream-video-js/issues/1090)) ([712f1e7](https://github.com/GetStream/stream-video-js/commit/712f1e7010fdb8859aaa6caba7e7d9e0f4557ccb)) ### [0.3.28](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.27...@stream-io/video-react-sdk-0.3.28) (2023-09-26) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.25` -* `@stream-io/video-styling` updated to version `0.1.9` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.25` +- `@stream-io/video-styling` updated to version `0.1.9` ### Features -* show the anonymous user count in the participant list ([#1109](https://github.com/GetStream/stream-video-js/issues/1109)) ([a253cbf](https://github.com/GetStream/stream-video-js/commit/a253cbfa7552a9ab4302ce824a72653a27dd324d)) +- show the anonymous user count in the participant list ([#1109](https://github.com/GetStream/stream-video-js/issues/1109)) ([a253cbf](https://github.com/GetStream/stream-video-js/commit/a253cbfa7552a9ab4302ce824a72653a27dd324d)) ### [0.3.27](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.26...@stream-io/video-react-sdk-0.3.27) (2023-09-26) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.24` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.24` + ### [0.3.26](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.25...@stream-io/video-react-sdk-0.3.26) (2023-09-25) ### Dependency Updates -* `@stream-io/i18n` updated to version `0.1.2` -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.23` -* `@stream-io/video-styling` updated to version `0.1.8` +- `@stream-io/i18n` updated to version `0.1.2` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.23` +- `@stream-io/video-styling` updated to version `0.1.8` ### Bug Fixes -* Add extra delay before attempting to play video in Safari and Firefox ([#1106](https://github.com/GetStream/stream-video-js/issues/1106)) ([5b4a589](https://github.com/GetStream/stream-video-js/commit/5b4a58918240a7b63807726609d6d54b92cfe1d2)) +- Add extra delay before attempting to play video in Safari and Firefox ([#1106](https://github.com/GetStream/stream-video-js/issues/1106)) ([5b4a589](https://github.com/GetStream/stream-video-js/commit/5b4a58918240a7b63807726609d6d54b92cfe1d2)) ### [0.3.25](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.24...@stream-io/video-react-sdk-0.3.25) (2023-09-20) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.22` -* `@stream-io/video-styling` updated to version `0.1.7` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.22` +- `@stream-io/video-styling` updated to version `0.1.7` ### Bug Fixes -* unmount video element when there is no video track or participant is invisible ([#1096](https://github.com/GetStream/stream-video-js/issues/1096)) ([bd01835](https://github.com/GetStream/stream-video-js/commit/bd01835f4e93c981ca2e5a7e4e09142ea4e326cf)), closes [#1094](https://github.com/GetStream/stream-video-js/issues/1094) +- unmount video element when there is no video track or participant is invisible ([#1096](https://github.com/GetStream/stream-video-js/issues/1096)) ([bd01835](https://github.com/GetStream/stream-video-js/commit/bd01835f4e93c981ca2e5a7e4e09142ea4e326cf)), closes [#1094](https://github.com/GetStream/stream-video-js/issues/1094) ### [0.3.24](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.23...@stream-io/video-react-sdk-0.3.24) (2023-09-19) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.21` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.21` + ### [0.3.23](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.22...@stream-io/video-react-sdk-0.3.23) (2023-09-18) ### Dependency Updates -* `@stream-io/video-styling` updated to version `0.1.6` +- `@stream-io/video-styling` updated to version `0.1.6` ### Bug Fixes -* hide the video element when a placeholder is visible ([#1094](https://github.com/GetStream/stream-video-js/issues/1094)) ([9efd84c](https://github.com/GetStream/stream-video-js/commit/9efd84cb77b98c372917e6bfa36161763969dddd)) +- hide the video element when a placeholder is visible ([#1094](https://github.com/GetStream/stream-video-js/issues/1094)) ([9efd84c](https://github.com/GetStream/stream-video-js/commit/9efd84cb77b98c372917e6bfa36161763969dddd)) ### [0.3.22](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.21...@stream-io/video-react-sdk-0.3.22) (2023-09-15) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.20` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.20` + ### [0.3.21](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.20...@stream-io/video-react-sdk-0.3.21) (2023-09-15) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.19` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.19` + ### [0.3.20](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.19...@stream-io/video-react-sdk-0.3.20) (2023-09-14) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.18` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.18` + ### [0.3.19](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.18...@stream-io/video-react-sdk-0.3.19) (2023-09-13) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.17` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.17` + ### [0.3.18](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.17...@stream-io/video-react-sdk-0.3.18) (2023-09-11) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.16` -### [0.3.17](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.16...@stream-io/video-react-sdk-0.3.17) (2023-09-08) +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.16` +### [0.3.17](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.16...@stream-io/video-react-sdk-0.3.17) (2023-09-08) ### Bug Fixes -* hook dependency issues, re-compute video aspect ratio after track unmute ([#1067](https://github.com/GetStream/stream-video-js/issues/1067)) ([392c36a](https://github.com/GetStream/stream-video-js/commit/392c36af9dbabd22f72d4cc4b11aab7b1d642b1f)) +- hook dependency issues, re-compute video aspect ratio after track unmute ([#1067](https://github.com/GetStream/stream-video-js/issues/1067)) ([392c36a](https://github.com/GetStream/stream-video-js/commit/392c36af9dbabd22f72d4cc4b11aab7b1d642b1f)) ### [0.3.16](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.15...@stream-io/video-react-sdk-0.3.16) (2023-09-05) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.15` -### [0.3.15](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.14...@stream-io/video-react-sdk-0.3.15) (2023-08-31) +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.15` +### [0.3.15](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.14...@stream-io/video-react-sdk-0.3.15) (2023-08-31) ### Features -* **react-sdk:** add browser permissions hook ([#972](https://github.com/GetStream/stream-video-js/issues/972)) ([4f1b40c](https://github.com/GetStream/stream-video-js/commit/4f1b40c3d19d580964c1e999c8055c3b736674a4)) +- **react-sdk:** add browser permissions hook ([#972](https://github.com/GetStream/stream-video-js/issues/972)) ([4f1b40c](https://github.com/GetStream/stream-video-js/commit/4f1b40c3d19d580964c1e999c8055c3b736674a4)) ### [0.3.14](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.13...@stream-io/video-react-sdk-0.3.14) (2023-08-31) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.14` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.14` + ### [0.3.13](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.12...@stream-io/video-react-sdk-0.3.13) (2023-08-31) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.13` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.13` + ### [0.3.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.11...@stream-io/video-react-sdk-0.3.12) (2023-08-30) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.12` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.12` + ### [0.3.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.10...@stream-io/video-react-sdk-0.3.11) (2023-08-30) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.11` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.11` ### Features -* **Call:** Dynascale support for Plain-JS SDK ([#914](https://github.com/GetStream/stream-video-js/issues/914)) ([d295fd3](https://github.com/GetStream/stream-video-js/commit/d295fd341bbe325310fc6479f24ef647b013429b)) +- **Call:** Dynascale support for Plain-JS SDK ([#914](https://github.com/GetStream/stream-video-js/issues/914)) ([d295fd3](https://github.com/GetStream/stream-video-js/commit/d295fd341bbe325310fc6479f24ef647b013429b)) ### [0.3.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.9...@stream-io/video-react-sdk-0.3.10) (2023-08-29) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.10` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.10` + ### [0.3.9](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.8...@stream-io/video-react-sdk-0.3.9) (2023-08-29) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.9` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.9` + ### [0.3.8](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.7...@stream-io/video-react-sdk-0.3.8) (2023-08-24) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.8` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.8` + ### [0.3.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.6...@stream-io/video-react-sdk-0.3.7) (2023-08-23) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.7` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.7` + ### [0.3.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.5...@stream-io/video-react-sdk-0.3.6) (2023-08-22) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.6` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.6` + ### [0.3.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.4...@stream-io/video-react-sdk-0.3.5) (2023-08-21) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.5` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.5` + ### [0.3.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.3...@stream-io/video-react-sdk-0.3.4) (2023-08-18) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.4` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.4` + ### [0.3.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.2...@stream-io/video-react-sdk-0.3.3) (2023-08-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.3` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.3` + ### [0.3.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.1...@stream-io/video-react-sdk-0.3.2) (2023-08-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.2` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.2` + ### [0.3.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.0...@stream-io/video-react-sdk-0.3.1) (2023-08-16) ### Dependency Updates -* `@stream-io/video-react-bindings` updated to version `0.2.1` +- `@stream-io/video-react-bindings` updated to version `0.2.1` + ## [0.3.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.8...@stream-io/video-react-sdk-0.3.0) (2023-08-16) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.2.0` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.2.0` ### ⚠ BREAKING CHANGES -* Call State reorganization (#931) +- Call State reorganization (#931) ### Features -* Call State reorganization ([#931](https://github.com/GetStream/stream-video-js/issues/931)) ([441dbd4](https://github.com/GetStream/stream-video-js/commit/441dbd4ffb8c851abb0ca719be143a1e80d1418c)), closes [#917](https://github.com/GetStream/stream-video-js/issues/917) +- Call State reorganization ([#931](https://github.com/GetStream/stream-video-js/issues/931)) ([441dbd4](https://github.com/GetStream/stream-video-js/commit/441dbd4ffb8c851abb0ca719be143a1e80d1418c)), closes [#917](https://github.com/GetStream/stream-video-js/issues/917) ### [0.2.8](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.7...@stream-io/video-react-sdk-0.2.8) (2023-08-15) ### Dependency Updates -* `@stream-io/video-styling` updated to version `0.1.5` +- `@stream-io/video-styling` updated to version `0.1.5` + ### [0.2.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.6...@stream-io/video-react-sdk-0.2.7) (2023-08-14) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.18` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.18` ### Features -* extra config params in goLive() API ([#924](https://github.com/GetStream/stream-video-js/issues/924)) ([e14a082](https://github.com/GetStream/stream-video-js/commit/e14a0829460a3c5ff6d249dd159e6118df0b8352)) +- extra config params in goLive() API ([#924](https://github.com/GetStream/stream-video-js/issues/924)) ([e14a082](https://github.com/GetStream/stream-video-js/commit/e14a0829460a3c5ff6d249dd159e6118df0b8352)) ### [0.2.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.5...@stream-io/video-react-sdk-0.2.6) (2023-08-11) - ### Features -* flag the dominant speaker with a CSS class ([#923](https://github.com/GetStream/stream-video-js/issues/923)) ([d503578](https://github.com/GetStream/stream-video-js/commit/d5035788c6f2b1a9db195d9f5fb9dd062cad1627)) +- flag the dominant speaker with a CSS class ([#923](https://github.com/GetStream/stream-video-js/issues/923)) ([d503578](https://github.com/GetStream/stream-video-js/commit/d5035788c6f2b1a9db195d9f5fb9dd062cad1627)) ### [0.2.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.4...@stream-io/video-react-sdk-0.2.5) (2023-08-11) ### Dependency Updates -* `@stream-io/video-react-bindings` updated to version `0.1.17` +- `@stream-io/video-react-bindings` updated to version `0.1.17` ### Features -* Wrap all call state hooks in useCallStateHooks() ([#917](https://github.com/GetStream/stream-video-js/issues/917)) ([19f891a](https://github.com/GetStream/stream-video-js/commit/19f891aab42b725b6a1d0194bf0ef8f645ccc792)) +- Wrap all call state hooks in useCallStateHooks() ([#917](https://github.com/GetStream/stream-video-js/issues/917)) ([19f891a](https://github.com/GetStream/stream-video-js/commit/19f891aab42b725b6a1d0194bf0ef8f645ccc792)) ### [0.2.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.3...@stream-io/video-react-sdk-0.2.4) (2023-08-10) ### Dependency Updates -* `@stream-io/video-react-bindings` updated to version `0.1.16` -### [0.2.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.2...@stream-io/video-react-sdk-0.2.3) (2023-08-10) +- `@stream-io/video-react-bindings` updated to version `0.1.16` +### [0.2.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.2...@stream-io/video-react-sdk-0.2.3) (2023-08-10) ### Bug Fixes -* **ParticipantView:** remove audio element while muted ([#918](https://github.com/GetStream/stream-video-js/issues/918)) ([076c7ff](https://github.com/GetStream/stream-video-js/commit/076c7ffbc4a525b0fb2acbc62a560734381e362b)) +- **ParticipantView:** remove audio element while muted ([#918](https://github.com/GetStream/stream-video-js/issues/918)) ([076c7ff](https://github.com/GetStream/stream-video-js/commit/076c7ffbc4a525b0fb2acbc62a560734381e362b)) ### [0.2.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.1...@stream-io/video-react-sdk-0.2.2) (2023-08-08) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.15` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.15` ### Features -* **livestream:** Livestream tutorial rewrite ([#909](https://github.com/GetStream/stream-video-js/issues/909)) ([49efdaa](https://github.com/GetStream/stream-video-js/commit/49efdaa14faccaa4848e8f9bdf3abb7748b925ac)) +- **livestream:** Livestream tutorial rewrite ([#909](https://github.com/GetStream/stream-video-js/issues/909)) ([49efdaa](https://github.com/GetStream/stream-video-js/commit/49efdaa14faccaa4848e8f9bdf3abb7748b925ac)) ### [0.2.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.2.0...@stream-io/video-react-sdk-0.2.1) (2023-08-07) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.14` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.14` + ## [0.2.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.18...@stream-io/video-react-sdk-0.2.0) (2023-08-07) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.13` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.13` ### ⚠ BREAKING CHANGES -* Server-side participant pinning (#881) +- Server-side participant pinning (#881) ### Features -* Server-side participant pinning ([#881](https://github.com/GetStream/stream-video-js/issues/881)) ([72829f1](https://github.com/GetStream/stream-video-js/commit/72829f1caf5b9c719d063a7e5175b7aa7431cd71)) +- Server-side participant pinning ([#881](https://github.com/GetStream/stream-video-js/issues/881)) ([72829f1](https://github.com/GetStream/stream-video-js/commit/72829f1caf5b9c719d063a7e5175b7aa7431cd71)) ### [0.1.18](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.17...@stream-io/video-react-sdk-0.1.18) (2023-08-04) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.12` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.12` + ### [0.1.17](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.16...@stream-io/video-react-sdk-0.1.17) (2023-08-01) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.11` -### [0.1.16](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.15...@stream-io/video-react-sdk-0.1.16) (2023-08-01) +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.11` +### [0.1.16](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.15...@stream-io/video-react-sdk-0.1.16) (2023-08-01) ### Documentation -* add prop description to client and call prop of StreamVideo and StreamCall ([#873](https://github.com/GetStream/stream-video-js/issues/873)) ([4d4a2b8](https://github.com/GetStream/stream-video-js/commit/4d4a2b81506af9cf7e81a4925cabc4429f32b401)) -* adjust Quickstart ([#872](https://github.com/GetStream/stream-video-js/issues/872)) ([42637a0](https://github.com/GetStream/stream-video-js/commit/42637a06c1b828ebd9285296be5a32a509c6c624)) -* rewrite video-calling tutorial ([#866](https://github.com/GetStream/stream-video-js/issues/866)) ([c16d0a2](https://github.com/GetStream/stream-video-js/commit/c16d0a283b005a77dfbcbb3bb7c9946dcc501094)) +- add prop description to client and call prop of StreamVideo and StreamCall ([#873](https://github.com/GetStream/stream-video-js/issues/873)) ([4d4a2b8](https://github.com/GetStream/stream-video-js/commit/4d4a2b81506af9cf7e81a4925cabc4429f32b401)) +- adjust Quickstart ([#872](https://github.com/GetStream/stream-video-js/issues/872)) ([42637a0](https://github.com/GetStream/stream-video-js/commit/42637a06c1b828ebd9285296be5a32a509c6c624)) +- rewrite video-calling tutorial ([#866](https://github.com/GetStream/stream-video-js/issues/866)) ([c16d0a2](https://github.com/GetStream/stream-video-js/commit/c16d0a283b005a77dfbcbb3bb7c9946dcc501094)) ### [0.1.15](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.14...@stream-io/video-react-sdk-0.1.15) (2023-07-28) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.10` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.10` ### Bug Fixes -* set initial device state regardless of call state ([#869](https://github.com/GetStream/stream-video-js/issues/869)) ([3c3cb29](https://github.com/GetStream/stream-video-js/commit/3c3cb29e5585e30b0eacc4b0ecb7bab2e075c111)) - +- set initial device state regardless of call state ([#869](https://github.com/GetStream/stream-video-js/issues/869)) ([3c3cb29](https://github.com/GetStream/stream-video-js/commit/3c3cb29e5585e30b0eacc4b0ecb7bab2e075c111)) ### Documentation -* **react-native:** UI Cookbook - Connection Quality Indicator ([#861](https://github.com/GetStream/stream-video-js/issues/861)) ([f9fc8fc](https://github.com/GetStream/stream-video-js/commit/f9fc8fc9653f29721989a52fd888b3db99b41cea)) +- **react-native:** UI Cookbook - Connection Quality Indicator ([#861](https://github.com/GetStream/stream-video-js/issues/861)) ([f9fc8fc](https://github.com/GetStream/stream-video-js/commit/f9fc8fc9653f29721989a52fd888b3db99b41cea)) ### [0.1.14](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.13...@stream-io/video-react-sdk-0.1.14) (2023-07-27) - ### Documentation -* Update audio room tutorial to support strict mode ([#840](https://github.com/GetStream/stream-video-js/issues/840)) ([9aec392](https://github.com/GetStream/stream-video-js/commit/9aec392ec4a44fb0c1eaee00c19568f01d7b3da9)) +- Update audio room tutorial to support strict mode ([#840](https://github.com/GetStream/stream-video-js/issues/840)) ([9aec392](https://github.com/GetStream/stream-video-js/commit/9aec392ec4a44fb0c1eaee00c19568f01d7b3da9)) ### [0.1.13](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.12...@stream-io/video-react-sdk-0.1.13) (2023-07-27) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.9` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.9` + ### [0.1.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.11...@stream-io/video-react-sdk-0.1.12) (2023-07-26) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.8` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.8` + ### [0.1.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.10...@stream-io/video-react-sdk-0.1.11) (2023-07-26) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.7` -### [0.1.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.9...@stream-io/video-react-sdk-0.1.10) (2023-07-25) +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.7` +### [0.1.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.9...@stream-io/video-react-sdk-0.1.10) (2023-07-25) ### Features -* **react-native:** add translations to SDK and DF app ([#828](https://github.com/GetStream/stream-video-js/issues/828)) ([c7a7f73](https://github.com/GetStream/stream-video-js/commit/c7a7f73b5cfd9222101e4c44b6c9ec42006bcac2)) +- **react-native:** add translations to SDK and DF app ([#828](https://github.com/GetStream/stream-video-js/issues/828)) ([c7a7f73](https://github.com/GetStream/stream-video-js/commit/c7a7f73b5cfd9222101e4c44b6c9ec42006bcac2)) ### [0.1.9](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.8...@stream-io/video-react-sdk-0.1.9) (2023-07-21) - ### Documentation -* Fix code snippet in video calling tutorial ([dc8f8cc](https://github.com/GetStream/stream-video-js/commit/dc8f8cc58d13b32eda2c7624152470c5909698e7)) +- Fix code snippet in video calling tutorial ([dc8f8cc](https://github.com/GetStream/stream-video-js/commit/dc8f8cc58d13b32eda2c7624152470c5909698e7)) ### [0.1.8](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.7...@stream-io/video-react-sdk-0.1.8) (2023-07-21) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.6` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.6` ### Bug Fixes -* strict mode issue ([#740](https://github.com/GetStream/stream-video-js/issues/740)) ([c39e4e4](https://github.com/GetStream/stream-video-js/commit/c39e4e4041a2326393478ad808b2aa791d50f8ce)) - +- strict mode issue ([#740](https://github.com/GetStream/stream-video-js/issues/740)) ([c39e4e4](https://github.com/GetStream/stream-video-js/commit/c39e4e4041a2326393478ad808b2aa791d50f8ce)) ### Documentation -* add backlinks to the main marketing pages ([#838](https://github.com/GetStream/stream-video-js/issues/838)) ([7374972](https://github.com/GetStream/stream-video-js/commit/7374972a93e6a6052b384a11e5883b7ccbb559ff)) +- add backlinks to the main marketing pages ([#838](https://github.com/GetStream/stream-video-js/issues/838)) ([7374972](https://github.com/GetStream/stream-video-js/commit/7374972a93e6a6052b384a11e5883b7ccbb559ff)) ### [0.1.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.6...@stream-io/video-react-sdk-0.1.7) (2023-07-21) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.5` -### [0.1.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.5...@stream-io/video-react-sdk-0.1.6) (2023-07-20) +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.5` +### [0.1.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.5...@stream-io/video-react-sdk-0.1.6) (2023-07-20) ### Bug Fixes -* Apply sinkId settings in paginated grid layout ([#829](https://github.com/GetStream/stream-video-js/issues/829)) ([017996b](https://github.com/GetStream/stream-video-js/commit/017996b42c3df3faaff40c15999880e65b3e097a)) +- Apply sinkId settings in paginated grid layout ([#829](https://github.com/GetStream/stream-video-js/issues/829)) ([017996b](https://github.com/GetStream/stream-video-js/commit/017996b42c3df3faaff40c15999880e65b3e097a)) ### [0.1.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.4...@stream-io/video-react-sdk-0.1.5) (2023-07-20) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.4` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.4` + ### [0.1.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.3...@stream-io/video-react-sdk-0.1.4) (2023-07-19) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.3` +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.3` + ### [0.1.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.2...@stream-io/video-react-sdk-0.1.3) (2023-07-18) ### Dependency Updates -* `@stream-io/video-client` updated to version `0.1.0` -* `@stream-io/video-react-bindings` updated to version `0.1.2` -### [0.1.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.1...@stream-io/video-react-sdk-0.1.2) (2023-07-17) +- `@stream-io/video-client` updated to version `0.1.0` +- `@stream-io/video-react-bindings` updated to version `0.1.2` +### [0.1.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.1...@stream-io/video-react-sdk-0.1.2) (2023-07-17) ### Features -* Trigger react-sdk release to test sample app deployment ([77abdb6](https://github.com/GetStream/stream-video-js/commit/77abdb67bafa6c33bf7b86070999f7ad9d6010df)) +- Trigger react-sdk release to test sample app deployment ([77abdb6](https://github.com/GetStream/stream-video-js/commit/77abdb67bafa6c33bf7b86070999f7ad9d6010df)) ### [0.1.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.1.0...@stream-io/video-react-sdk-0.1.1) (2023-07-17) diff --git a/packages/react-sdk/docusaurus/docs/React/03-ui-components/call/call-controls.mdx b/packages/react-sdk/docusaurus/docs/React/03-ui-components/call/call-controls.mdx index f502547c78..c3b18dafd9 100644 --- a/packages/react-sdk/docusaurus/docs/React/03-ui-components/call/call-controls.mdx +++ b/packages/react-sdk/docusaurus/docs/React/03-ui-components/call/call-controls.mdx @@ -71,6 +71,18 @@ This component can be used to leave a call or to reject an incoming/outgoing cal | `onClick` | Event handler to override the default click behavior | `MouseEventHandler` \| `undefined` | | `onLeave` | Custom event handler to call after the call was left | `() => void` \| `undefined` | +### `CancelCallConfirmButton` + +This component can be used to leave a call or end the call for all participants but only if the particpant has the requiredGrants `OwnCapability.END_CALL`. + +#### Props + +| Name | Description | Type | +| ---------- | ---------------------------------------------------- | ----------------------------------------------------- | +| `disabled` | Set `true` to disable the button | `boolean` \| `undefined` | +| `onClick` | Event handler to override the default click behavior | `MouseEventHandler` \| `undefined` | +| `onLeave` | Custom event handler to call after the call was left | `() => void` \| `undefined` | + ### `ReactionsButton` This component can be used to send reactions during a call. The following reactions are supported by default: @@ -95,6 +107,16 @@ This component can be used to start/stop call recording. It's also used as a vis | --------- | ------------------------------------------------------- | ----------------------- | | `caption` | The explanatory text displayed under the control button | `string` \| `undefined` | +### `RecordCallConfirmationButton` + +This component can be used to start/stop a call recording. A confirmation will be show to let the particpant cancel or confirm to ending the recording that is in progress. + +#### Props + +| Name | Description | Type | +| --------- | ------------------------------------------------------- | ----------------------- | +| `caption` | The explanatory text displayed under the control button | `string` \| `undefined` | + ### `ScreenShareButton` This component can be used to start/stop screen sharing. It's also used as a visual indicator to let participants know if there is an ongoing screen share. This component only allows a single ongoing screen share. diff --git a/packages/react-sdk/docusaurus/docs/React/03-ui-components/participants/device-settings.mdx b/packages/react-sdk/docusaurus/docs/React/03-ui-components/participants/device-settings.mdx index 7d7f80dcff..66e8323671 100644 --- a/packages/react-sdk/docusaurus/docs/React/03-ui-components/participants/device-settings.mdx +++ b/packages/react-sdk/docusaurus/docs/React/03-ui-components/participants/device-settings.mdx @@ -84,37 +84,30 @@ const MyDevicePanel = () => { ### DeviceSettings -None +| Name | Description | Type | Default | +| ------------ | -------------------------------------------- | ----------------------------------- | ------- | +| `visualType` | Visually display a list or dropdown selector | `list` \| `dropdown` \| `undefined` | `list` | ### DeviceSelectorVideo -### `title` - -| Type | Default | -| ----------------------- | ------------------- | -| `string` \| `undefined` | `'Select a Camera'` | - -String displayed at the top of the selector component. +| Name | Description | Type | Default | +| ------------ | ------------------------------------------------------- | ----------------------------------- | ----------------- | +| `title` | Title is displayed at the top of the selector component | `string` \| `undefined` | `Select a Camera` | +| `visualType` | Visually display a list or dropdown selector | `list` \| `dropdown` \| `undefined` | `list` | ### DeviceSelectorAudioInput -### `title` - -| Type | Default | -| ----------------------- | ---------------- | -| `string` \| `undefined` | `'Select a Mic'` | - -String displayed at the top of the selector component. +| Name | Description | Type | Default | +| ------------ | ------------------------------------------------------- | ----------------------------------- | -------------- | +| `title` | Title is displayed at the top of the selector component | `string` \| `undefined` | `Select a Mic` | +| `visualType` | Visually display a list or dropdown selector | `list` \| `dropdown` \| `undefined` | `list` | ### DeviceSelectorAudioOutput -### `title` - -| Type | Default | -| ----------------------- | ------------------- | -| `string` \| `undefined` | `'Select Speakers'` | - -String displayed at the top of the selector component. +| Name | Description | Type | Default | +| ------------ | ------------------------------------------------------- | ----------------------------------- | ------------------ | +| `title` | Title is displayed at the top of the selector component | `string` \| `undefined` | `Select a Speaker` | +| `visualType` | Visually display a list or dropdown selector | `list` \| `dropdown` \| `undefined` | `list` | ## Customization diff --git a/packages/react-sdk/docusaurus/docs/React/03-ui-components/utility/dropdown-select.mdx b/packages/react-sdk/docusaurus/docs/React/03-ui-components/utility/dropdown-select.mdx new file mode 100644 index 0000000000..0a1b2aa0a6 --- /dev/null +++ b/packages/react-sdk/docusaurus/docs/React/03-ui-components/utility/dropdown-select.mdx @@ -0,0 +1,28 @@ +--- +id: dropdown-select +title: Dropdown select +--- + +import DropdownSelect from '../../assets/03-ui-components/dropdown-select.png'; + +The `DropDownSelect` utility component can be used to to simply create a dropdown select component. + +#### Props + +| Name | Description | Type | +| ---------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `icon` | Icon to be shown next to the label of the selected item | `string` \| `undefined` | +| `defaultSelectedLabel` | Label to be shown on default | `string` | +| `defaultSelectedIndex` | Sets the default selected option | `number` | +| `handleSelect` | Handler to handle the selected option | `(index: number) => void` | +| `children` | DropDownSelectOption | `ReactElement` \| `ReactElement` | + +#### DropDownSelectOptionProps + +You can provide the `DropdownSelectOption` as a child to the `DropdownSelect` + +| Name | Description | Type | +| ---------- | ----------- | ------------------------ | +| `label` | | `string` | +| `selected` | | `boolean` \| `undefined` | +| `icon` | | `string` | diff --git a/packages/react-sdk/docusaurus/docs/React/03-ui-components/utility/recording-in-progress-notification.mdx b/packages/react-sdk/docusaurus/docs/React/03-ui-components/utility/recording-in-progress-notification.mdx new file mode 100644 index 0000000000..22ff5a52e0 --- /dev/null +++ b/packages/react-sdk/docusaurus/docs/React/03-ui-components/utility/recording-in-progress-notification.mdx @@ -0,0 +1,45 @@ +--- +id: recording-in-progress-notification +title: Recording in progress notification +--- + +import RecordingInProgressNotification from '../../assets/03-ui-components/recording-in-progress-notification.png'; + +The `RecordingInProgressNotification` component is based on `Notification` component that is a simple popover. The SDK attaches the component to [`RecordCallButton`](../../call/call-controls). The popover rendering is controlled by detecting if there is a recording in progress, `useToggleCallRecording()`. + +
+ Default UI of RecordingInProgressNotification component +
+ +## General usage + +The component is used by wrapping the component to which it will refer: + +```tsx + + + +``` + +## Props + +| Name | Type | Default | +| ------ | ----------------------- | ---------------------------- | +| `text` | `string` \| `undefined` | `'Recording in progress...'` | + +Text message displayed by the notification. + +## Customization + +To learn more about the recording functionality, have a look at our [call-controls](../../../ui-components/call/call-controls). diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/avatar.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/avatar.png index 9009db5b8b..787d755c70 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/avatar.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/avatar.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-controls.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-controls.png index f66aed750e..dc7538e404 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-controls.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-controls.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-admin-actions-menu.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-admin-actions-menu.png index 6a9073d7b7..cfdf25f47b 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-admin-actions-menu.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-admin-actions-menu.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-user-actions-menu.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-user-actions-menu.png index da4048725d..b4bd4e2573 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-user-actions-menu.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list-with-user-actions-menu.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list.png index 05b4bdefb8..36d5baa533 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-participant-list/call-participant-list.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-recording-list/empty-call-recording-list.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-recording-list/empty-call-recording-list.png index 54c92f7220..84336f5f58 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-recording-list/empty-call-recording-list.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-recording-list/empty-call-recording-list.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-stats.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-stats.png index 6391e07a0d..4b6de4b9a7 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-stats.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/call-stats.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-audio-input.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-audio-input.png index 223a949a2b..fe8f2569cb 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-audio-input.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-audio-input.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-video.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-video.png index 83f87f2f02..2c6d1a401a 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-video.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-selector-video.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-settings.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-settings.png index 1a4fff165f..4469592b20 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-settings.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/device-settings.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/dropdown-select.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/dropdown-select.png new file mode 100644 index 0000000000..1be57c45ab Binary files /dev/null and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/dropdown-select.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/participant.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/participant.png index 85f79657cc..f09c54cddc 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/participant.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/participant.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/reaction.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/reaction.png index faf9217a04..6491d8bc3e 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/reaction.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/reaction.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/recording-in-progress-notification.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/recording-in-progress-notification.png new file mode 100644 index 0000000000..bc647df29b Binary files /dev/null and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/recording-in-progress-notification.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/speaking-while-muted-notification.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/speaking-while-muted-notification.png index f09a9877fa..01d46bca18 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/speaking-while-muted-notification.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/speaking-while-muted-notification.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/disabled-video-preview.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/disabled-video-preview.png index d2e4d80356..9ccb0c830c 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/disabled-video-preview.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/disabled-video-preview.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/no-camera-found-video-preview.png b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/no-camera-found-video-preview.png index 880980c5c2..3ffd670de8 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/no-camera-found-video-preview.png and b/packages/react-sdk/docusaurus/docs/React/assets/03-ui-components/video-preview/no-camera-found-video-preview.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-participant-label-muted.png b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-participant-label-muted.png index 2277aa1da9..7e97a8bcf8 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-participant-label-muted.png and b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-participant-label-muted.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-user-label-connection-dominant-speaker.png b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-user-label-connection-dominant-speaker.png index e0592ad0dd..7f77512419 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-user-label-connection-dominant-speaker.png and b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/03-custom-label/default-user-label-connection-dominant-speaker.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/14-participant-view-customizations/participant-view-ui.png b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/14-participant-view-customizations/participant-view-ui.png index b75b044be2..92b57067c1 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/14-participant-view-customizations/participant-view-ui.png and b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/14-participant-view-customizations/participant-view-ui.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/15-watching-a-livestream/normal-mode.png b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/15-watching-a-livestream/normal-mode.png index 636fc157ca..d898d4ead3 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/15-watching-a-livestream/normal-mode.png and b/packages/react-sdk/docusaurus/docs/React/assets/06-ui-cookbook/15-watching-a-livestream/normal-mode.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/lobby-preview.png b/packages/react-sdk/docusaurus/docs/React/assets/lobby-preview.png index af0d8ef341..759efbc505 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/lobby-preview.png and b/packages/react-sdk/docusaurus/docs/React/assets/lobby-preview.png differ diff --git a/packages/react-sdk/docusaurus/docs/React/assets/no-video-fallback-avatar.png b/packages/react-sdk/docusaurus/docs/React/assets/no-video-fallback-avatar.png index 06396dd7c8..b35c64f5d2 100644 Binary files a/packages/react-sdk/docusaurus/docs/React/assets/no-video-fallback-avatar.png and b/packages/react-sdk/docusaurus/docs/React/assets/no-video-fallback-avatar.png differ diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index a03b83a9f1..8eec8ce4d9 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -28,13 +28,12 @@ "CHANGELOG.md" ], "dependencies": { - "@floating-ui/react": "^0.26.1", - "@nivo/core": "^0.80.0", - "@nivo/line": "^0.80.0", + "@floating-ui/react": "^0.26.5", "@stream-io/video-client": "workspace:^", "@stream-io/video-react-bindings": "workspace:^", + "chart.js": "^4.4.1", "clsx": "^2.0.0", - "prop-types": "^15.8.1" + "react-chartjs-2": "^5.2.0" }, "peerDependencies": { "react": "^18.0.0", @@ -45,7 +44,6 @@ "@rollup/plugin-replace": "^5.0.4", "@rollup/plugin-typescript": "^11.1.5", "@stream-io/video-styling": "workspace:^", - "@types/prop-types": "^15.7.8", "react": "^18.2.0", "react-dom": "^18.2.0", "rimraf": "^5.0.5", diff --git a/packages/react-sdk/src/components/Button/CompositeButton.tsx b/packages/react-sdk/src/components/Button/CompositeButton.tsx index 730aef0b66..638073d247 100644 --- a/packages/react-sdk/src/components/Button/CompositeButton.tsx +++ b/packages/react-sdk/src/components/Button/CompositeButton.tsx @@ -1,33 +1,85 @@ import clsx from 'clsx'; import { MenuToggle, ToggleMenuButtonProps } from '../Menu'; -import { ComponentType, forwardRef, PropsWithChildren } from 'react'; -import { useI18n } from '@stream-io/video-react-bindings'; +import { + ComponentProps, + ComponentType, + forwardRef, + JSX, + PropsWithChildren, +} from 'react'; import { Placement } from '@floating-ui/react'; import { IconButton } from './IconButton'; import { isComponentType } from '../../utilities'; -export type IconButtonWithMenuProps = PropsWithChildren<{ - active?: boolean; - Menu?: ComponentType | JSX.Element; - caption?: string; - menuPlacement?: Placement; -}>; +export type IconButtonWithMenuProps = + PropsWithChildren<{ + active?: boolean; + Menu?: ComponentType | JSX.Element; + caption?: string; + className?: string; + menuPlacement?: Placement; + menuOffset?: number; + ToggleMenuButton?: ComponentType>; + variant?: 'primary' | 'secondary'; + }> & + ComponentProps<'button'>; export const CompositeButton = forwardRef< HTMLDivElement, IconButtonWithMenuProps ->(({ caption, children, active, Menu, menuPlacement }, ref) => { +>(function CompositeButton( + { + caption, + children, + className, + active, + Menu, + menuPlacement, + menuOffset, + title, + ToggleMenuButton = DefaultToggleMenuButton, + variant, + onClick, + ...restButtonProps + }, + ref, +) { return ( -
+
- {children} + {Menu && ( - + {isComponentType(Menu) ? : Menu} )} @@ -39,17 +91,17 @@ export const CompositeButton = forwardRef< ); }); -const ToggleMenuButton = forwardRef( - ({ menuShown }, ref) => { - const { t } = useI18n(); - - return ( - - ); - }, -); +const DefaultToggleMenuButton = forwardRef< + HTMLButtonElement, + ToggleMenuButtonProps +>(function DefaultToggleMenuButton({ menuShown }, ref) { + return ( + + ); +}); diff --git a/packages/react-sdk/src/components/Button/CopyToClipboardButton.tsx b/packages/react-sdk/src/components/Button/CopyToClipboardButton.tsx deleted file mode 100644 index 74ded53c3e..0000000000 --- a/packages/react-sdk/src/components/Button/CopyToClipboardButton.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { - ComponentProps, - ComponentPropsWithRef, - ComponentType, - ForwardedRef, - forwardRef, - MouseEventHandler, - useCallback, - useState, -} from 'react'; -import clsx from 'clsx'; -import { Placement } from '@floating-ui/react'; - -import { Tooltip } from '../Tooltip'; - -type CopyToClipboardButtonProps = ComponentProps<'button'> & { - /** Custom button component implementation. Will be rendered instead of native button element */ - Button?: ComponentType< - ComponentPropsWithRef<'button'> & { ref: ForwardedRef } - >; - /** Custom function to override the logic of generating the call invitation link */ - copyValue: (() => string) | string; - /** Callback invoked if the copy-to-clipboard action fails */ - onError?: (button: HTMLButtonElement, e: Error) => void; - /** Callback invoked if the copy-to-clipboard action succeeds */ - onSuccess?: (button: HTMLButtonElement) => void; -}; - -export const CopyToClipboardButton = forwardRef< - HTMLButtonElement, - CopyToClipboardButtonProps ->( - ( - { Button, className, copyValue, onClick, onError, onSuccess, ...restProps }, - ref, - ) => { - const handleClick: MouseEventHandler = useCallback( - async (event) => { - if (onClick) onClick(event); - const value = typeof copyValue === 'function' ? copyValue() : copyValue; - try { - await navigator?.clipboard.writeText(value); - onSuccess?.(event.target as HTMLButtonElement); - } catch (error) { - onError?.(event.target as HTMLButtonElement, error as Error); - } - }, - [copyValue, onClick, onError, onSuccess], - ); - - const props = { - ...restProps, - ref: ref, - className: clsx('str-video__copy-to-clipboard-button', className), - onClick: handleClick, - }; - - return Button ? - ); - }, -); +export const IconButton = forwardRef(function IconButton( + props: ButtonWithIconProps, + ref: ForwardedRef, +) { + const { icon, enabled, variant, onClick, className, ...rest } = props; + return ( + + ); +}); diff --git a/packages/react-sdk/src/components/Button/index.ts b/packages/react-sdk/src/components/Button/index.ts index c801325a6a..1775408668 100644 --- a/packages/react-sdk/src/components/Button/index.ts +++ b/packages/react-sdk/src/components/Button/index.ts @@ -1,4 +1,3 @@ export * from './CompositeButton'; -export * from './CopyToClipboardButton'; export * from './IconButton'; export * from './TextButton'; diff --git a/packages/react-sdk/src/components/CallControls/AcceptCallButton.tsx b/packages/react-sdk/src/components/CallControls/AcceptCallButton.tsx index 0bc85e0b43..642e98a9e4 100644 --- a/packages/react-sdk/src/components/CallControls/AcceptCallButton.tsx +++ b/packages/react-sdk/src/components/CallControls/AcceptCallButton.tsx @@ -30,6 +30,7 @@ export const AcceptCallButton = ({ disabled={disabled} icon="call-accept" variant="success" + data-testid="accept-call-button" onClick={handleClick} /> ); diff --git a/packages/react-sdk/src/components/CallControls/CallControls.tsx b/packages/react-sdk/src/components/CallControls/CallControls.tsx index 827daf9f8b..54e39ed0bf 100644 --- a/packages/react-sdk/src/components/CallControls/CallControls.tsx +++ b/packages/react-sdk/src/components/CallControls/CallControls.tsx @@ -1,6 +1,6 @@ import { SpeakingWhileMutedNotification } from '../Notification'; import { RecordCallButton } from './RecordCallButton'; -import { CallStatsButton } from './CallStatsButton'; +import { ReactionsButton } from './ReactionsButton'; import { ScreenShareButton } from './ScreenShareButton'; import { ToggleAudioPublishingButton } from './ToggleAudioButton'; import { ToggleVideoPublishingButton } from './ToggleVideoButton'; @@ -13,7 +13,7 @@ export type CallControlsProps = { export const CallControls = ({ onLeave }: CallControlsProps) => (
- + diff --git a/packages/react-sdk/src/components/CallControls/CallStatsButton.tsx b/packages/react-sdk/src/components/CallControls/CallStatsButton.tsx index dbfc6e0b12..0a774c4985 100644 --- a/packages/react-sdk/src/components/CallControls/CallStatsButton.tsx +++ b/packages/react-sdk/src/components/CallControls/CallStatsButton.tsx @@ -1,8 +1,14 @@ import { forwardRef } from 'react'; +import { useI18n } from '@stream-io/video-react-bindings'; import { CallStats } from '../CallStats'; -import { CompositeButton, IconButton } from '../Button/'; +import { CompositeButton } from '../Button/'; import { MenuToggle, ToggleMenuButtonProps } from '../Menu'; +import { Icon } from '../Icon'; + +export type CallStatsButtonProps = { + caption?: string; +}; export const CallStatsButton = () => ( @@ -12,9 +18,20 @@ export const CallStatsButton = () => ( const ToggleMenuButton = forwardRef< HTMLDivElement, - ToggleMenuButtonProps ->(({ menuShown }, ref) => ( - - - -)); + ToggleMenuButtonProps & CallStatsButtonProps +>(function ToggleMenuButton(props, ref) { + const { t } = useI18n(); + const { caption, menuShown } = props; + + return ( + + + + ); +}); diff --git a/packages/react-sdk/src/components/CallControls/CancelCallButton.tsx b/packages/react-sdk/src/components/CallControls/CancelCallButton.tsx index 33ef637561..82bdb3bb99 100644 --- a/packages/react-sdk/src/components/CallControls/CancelCallButton.tsx +++ b/packages/react-sdk/src/components/CallControls/CancelCallButton.tsx @@ -1,6 +1,65 @@ -import { MouseEventHandler, useCallback } from 'react'; -import { IconButton } from '../Button/'; -import { useCall } from '@stream-io/video-react-bindings'; +import { forwardRef, MouseEventHandler, useCallback } from 'react'; +import { OwnCapability } from '@stream-io/video-client'; +import { Restricted, useCall, useI18n } from '@stream-io/video-react-bindings'; + +import { MenuToggle, ToggleMenuButtonProps } from '../Menu'; + +import { IconButton } from '../Button'; +import { Icon } from '../Icon'; + +const EndCallMenu = (props: { + onLeave: MouseEventHandler; + onEnd: MouseEventHandler; +}) => { + const { onLeave, onEnd } = props; + const { t } = useI18n(); + return ( +
+ + + + +
+ ); +}; + +const CancelCallToggleMenuButton = forwardRef< + HTMLButtonElement, + ToggleMenuButtonProps +>(function CancelCallToggleMenuButton(props, ref) { + const { t } = useI18n(); + return ( + + ); +}); export type CancelCallButtonProps = { disabled?: boolean; @@ -8,12 +67,50 @@ export type CancelCallButtonProps = { onLeave?: () => void; }; +export const CancelCallConfirmButton = ({ + onClick, + onLeave, +}: CancelCallButtonProps) => { + const call = useCall(); + + const handleLeave: MouseEventHandler = useCallback( + async (e) => { + if (onClick) { + onClick(e); + } else if (call) { + await call.leave(); + onLeave?.(); + } + }, + [onClick, onLeave, call], + ); + + const handleEndCall: MouseEventHandler = useCallback( + async (e) => { + if (onClick) { + onClick(e); + } else if (call) { + await call.endCall(); + onLeave?.(); + } + }, + [onClick, onLeave, call], + ); + + return ( + + + + ); +}; + export const CancelCallButton = ({ disabled, onClick, onLeave, }: CancelCallButtonProps) => { const call = useCall(); + const { t } = useI18n(); const handleClick: MouseEventHandler = useCallback( async (e) => { if (onClick) { @@ -30,6 +127,8 @@ export const CancelCallButton = ({ disabled={disabled} icon="call-end" variant="danger" + title={t('Leave call')} + data-testid="cancel-call-button" onClick={handleClick} /> ); diff --git a/packages/react-sdk/src/components/CallControls/ReactionsButton.tsx b/packages/react-sdk/src/components/CallControls/ReactionsButton.tsx index 947765ccd3..e06c2d801d 100644 --- a/packages/react-sdk/src/components/CallControls/ReactionsButton.tsx +++ b/packages/react-sdk/src/components/CallControls/ReactionsButton.tsx @@ -1,8 +1,13 @@ +import { forwardRef } from 'react'; +import clsx from 'clsx'; + import { OwnCapability, StreamReaction } from '@stream-io/video-client'; import { Restricted, useCall, useI18n } from '@stream-io/video-react-bindings'; -import { CompositeButton, IconButton } from '../Button'; +import { MenuToggle, MenuVisualType, ToggleMenuButtonProps } from '../Menu'; +import { CompositeButton } from '../Button'; import { defaultEmojiReactionMap } from '../Reaction'; +import { Icon } from '../Icon'; export const defaultReactions: StreamReaction[] = [ { @@ -39,38 +44,53 @@ export interface ReactionsButtonProps { export const ReactionsButton = ({ reactions = defaultReactions, }: ReactionsButtonProps) => { - const { t } = useI18n(); - return ( - } + - { - console.log('Reactions'); - }} - /> - + +
); }; +const ToggleReactionsMenuButton = forwardRef< + HTMLDivElement, + ToggleMenuButtonProps +>(function ToggleReactionsMenuButton({ menuShown }, ref) { + const { t } = useI18n(); + return ( + + + + ); +}); + export interface DefaultReactionsMenuProps { reactions: StreamReaction[]; + layout?: 'horizontal' | 'vertical'; } export const DefaultReactionsMenu = ({ reactions, + layout = 'horizontal', }: DefaultReactionsMenuProps) => { const call = useCall(); return ( -
+
{reactions.map((reaction) => ( + icon="close" + />
); }; diff --git a/packages/react-sdk/src/components/CallParticipantsList/CallParticipantListingItem.tsx b/packages/react-sdk/src/components/CallParticipantsList/CallParticipantListingItem.tsx index 83a19e1edf..ab92a4ab26 100644 --- a/packages/react-sdk/src/components/CallParticipantsList/CallParticipantListingItem.tsx +++ b/packages/react-sdk/src/components/CallParticipantsList/CallParticipantListingItem.tsx @@ -1,31 +1,15 @@ import clsx from 'clsx'; -import { - ComponentProps, - ComponentType, - forwardRef, - useEffect, - useState, -} from 'react'; -import { - Restricted, - useCall, - useConnectedUser, - useI18n, -} from '@stream-io/video-react-bindings'; -import { - OwnCapability, - SfuModels, - StreamVideoParticipant, -} from '@stream-io/video-client'; +import { ComponentProps, ComponentType, forwardRef } from 'react'; +import { useConnectedUser, useI18n } from '@stream-io/video-react-bindings'; +import { SfuModels, StreamVideoParticipant } from '@stream-io/video-client'; import { IconButton } from '../Button'; -import { - GenericMenu, - GenericMenuButtonItem, - MenuToggle, - ToggleMenuButtonProps, -} from '../Menu'; +import { MenuToggle, ToggleMenuButtonProps } from '../Menu'; import { WithTooltip } from '../Tooltip'; -import { Icon } from '../Icon'; +import { Avatar } from '../Avatar'; +import { + ParticipantActionsContextMenu, + ParticipantViewContext, +} from '../../core/'; type CallParticipantListingItemProps = { /** Participant object be rendered */ @@ -49,6 +33,7 @@ export const CallParticipantListingItem = ({ return (
+
- + + +
@@ -95,7 +84,7 @@ type DisplayNameProps = { /** Participant object that provides the data from which display name can be generated */ participant: StreamVideoParticipant; }; -// todo: implement display device flag + const DefaultDisplayName = ({ participant }: DisplayNameProps) => { const connectedUser = useConnectedUser(); const { t } = useI18n(); @@ -122,260 +111,7 @@ const DefaultDisplayName = ({ participant }: DisplayNameProps) => { }; const ToggleButton = forwardRef( - (props, ref) => { + function ToggleButton(props, ref) { return ; }, ); - -export const ParticipantActionsContextMenu = ({ - participant, - participantViewElement, - videoElement, -}: { - participant: StreamVideoParticipant; - participantViewElement?: HTMLDivElement | null; - videoElement?: HTMLVideoElement | null; -}) => { - const [fullscreenModeOn, setFullscreenModeOn] = useState( - !!document.fullscreenElement, - ); - const [pictureInPictureElement, setPictureInPictureElement] = useState( - document.pictureInPictureElement, - ); - const call = useCall(); - const { t } = useI18n(); - - const { pin, publishedTracks, sessionId, userId } = participant; - - const hasAudio = publishedTracks.includes(SfuModels.TrackType.AUDIO); - const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO); - const hasScreenShare = publishedTracks.includes( - SfuModels.TrackType.SCREEN_SHARE, - ); - const hasScreenShareAudio = publishedTracks.includes( - SfuModels.TrackType.SCREEN_SHARE_AUDIO, - ); - - const blockUser = () => call?.blockUser(userId); - const muteAudio = () => call?.muteUser(userId, 'audio'); - const muteVideo = () => call?.muteUser(userId, 'video'); - const muteScreenShare = () => call?.muteUser(userId, 'screenshare'); - const muteScreenShareAudio = () => - call?.muteUser(userId, 'screenshare_audio'); - - const grantPermission = (permission: string) => () => { - call?.updateUserPermissions({ - user_id: userId, - grant_permissions: [permission], - }); - }; - - const revokePermission = (permission: string) => () => { - call?.updateUserPermissions({ - user_id: userId, - revoke_permissions: [permission], - }); - }; - - const toggleParticipantPinnedAt = () => { - if (pin) { - call?.unpin(sessionId); - } else { - call?.pin(sessionId); - } - }; - - const pinForEveryone = () => { - call - ?.pinForEveryone({ - user_id: userId, - session_id: sessionId, - }) - .catch((err) => { - console.error(`Failed to pin participant ${userId}`, err); - }); - }; - - const unpinForEveryone = () => { - call - ?.unpinForEveryone({ - user_id: userId, - session_id: sessionId, - }) - .catch((err) => { - console.error(`Failed to unpin participant ${userId}`, err); - }); - }; - - const toggleFullscreenMode = () => { - if (!fullscreenModeOn) { - return participantViewElement - ?.requestFullscreen() - .then(() => setFullscreenModeOn(true)) - .catch(console.error); - } - - document - .exitFullscreen() - .catch(console.error) - .finally(() => setFullscreenModeOn(false)); - }; - - useEffect(() => { - // handles the case when fullscreen mode is toggled externally, - // e.g., by pressing ESC key or some other keyboard shortcut - const handleFullscreenChange = () => { - setFullscreenModeOn(!!document.fullscreenElement); - }; - document.addEventListener('fullscreenchange', handleFullscreenChange); - return () => { - document.removeEventListener('fullscreenchange', handleFullscreenChange); - }; - }, []); - - useEffect(() => { - if (!videoElement) return; - - const handlePictureInPicture = () => { - setPictureInPictureElement(document.pictureInPictureElement); - }; - - videoElement.addEventListener( - 'enterpictureinpicture', - handlePictureInPicture, - ); - videoElement.addEventListener( - 'leavepictureinpicture', - handlePictureInPicture, - ); - - return () => { - videoElement.removeEventListener( - 'enterpictureinpicture', - handlePictureInPicture, - ); - videoElement.removeEventListener( - 'leavepictureinpicture', - handlePictureInPicture, - ); - }; - }, [videoElement]); - - const togglePictureInPicture = () => { - if (videoElement && pictureInPictureElement !== videoElement) { - return videoElement - .requestPictureInPicture() - .catch(console.error) as Promise; - } - - document.exitPictureInPicture().catch(console.error); - }; - - return ( - - - - {pin ? t('Unpin') : t('Pin')} - - - - - {t('Pin for everyone')} - - - - {t('Unpin for everyone')} - - - - - - {t('Block')} - - - - - - {t('Turn off video')} - - - - {t('Turn off screen share')} - - - - {t('Mute audio')} - - - - {t('Mute screen share audio')} - - - {participantViewElement && ( - - {t('{{ direction }} fullscreen', { - direction: fullscreenModeOn ? t('Leave') : t('Enter'), - })} - - )} - {videoElement && document.pictureInPictureEnabled && ( - - {t('{{ direction }} picture-in-picture', { - direction: - pictureInPictureElement === videoElement - ? t('Leave') - : t('Enter'), - })} - - )} - - - {t('Allow audio')} - - - {t('Allow video')} - - - {t('Allow screen sharing')} - - - - {t('Disable audio')} - - - {t('Disable video')} - - - {t('Disable screen sharing')} - - - - ); -}; diff --git a/packages/react-sdk/src/components/CallParticipantsList/CallParticipantsList.tsx b/packages/react-sdk/src/components/CallParticipantsList/CallParticipantsList.tsx index 8b5d61b650..fbf59d1b1d 100644 --- a/packages/react-sdk/src/components/CallParticipantsList/CallParticipantsList.tsx +++ b/packages/react-sdk/src/components/CallParticipantsList/CallParticipantsList.tsx @@ -1,7 +1,5 @@ import { - ComponentProps, Dispatch, - ForwardedRef, forwardRef, SetStateAction, useCallback, @@ -17,14 +15,9 @@ import { OwnCapability, StreamVideoParticipant, } from '@stream-io/video-client'; -import clsx from 'clsx'; import { BlockedUserListing } from './BlockedUserListing'; -import { - CopyToClipboardButtonWithPopup, - IconButton, - TextButton, -} from '../Button'; +import { IconButton, TextButton } from '../Button'; import { CallParticipantListHeader } from './CallParticipantListHeader'; import { CallParticipantListing } from './CallParticipantListing'; import { EmptyParticipantSearchList } from './EmptyParticipantSearchList'; @@ -97,12 +90,6 @@ export const CallParticipantsList = ({ /> )}
-
- -
); }; @@ -123,7 +110,6 @@ const CallParticipantListContentHeader = ({ return (
- {UserListTypes[userListType]} {userListType === 'active' && ( ( - (props, ref) => { + function ToggleButton(props, ref) { return ; }, ); - -const InviteLinkButton = forwardRef( - ( - { className, ...props }: ComponentProps<'button'>, - ref: ForwardedRef, - ) => ( - - ), -); diff --git a/packages/react-sdk/src/components/CallRecordingList/CallRecordingList.tsx b/packages/react-sdk/src/components/CallRecordingList/CallRecordingList.tsx index 102086e807..981ba86487 100644 --- a/packages/react-sdk/src/components/CallRecordingList/CallRecordingList.tsx +++ b/packages/react-sdk/src/components/CallRecordingList/CallRecordingList.tsx @@ -51,12 +51,30 @@ export const CallRecordingList = ({ {loading ? ( ) : callRecordings.length ? ( - callRecordings.map((recording) => ( - - )) + <> +
    +
  • +
    + Name +
    +
    + Start time +
    +
    + End time +
    +
    +
  • +
+
    + {callRecordings.map((recording) => ( + + ))} +
+ ) : ( )} diff --git a/packages/react-sdk/src/components/CallRecordingList/CallRecordingListHeader.tsx b/packages/react-sdk/src/components/CallRecordingList/CallRecordingListHeader.tsx index d56770c77b..e92a084691 100644 --- a/packages/react-sdk/src/components/CallRecordingList/CallRecordingListHeader.tsx +++ b/packages/react-sdk/src/components/CallRecordingList/CallRecordingListHeader.tsx @@ -1,4 +1,5 @@ import { CallRecording } from '@stream-io/video-client'; +import { useI18n } from '@stream-io/video-react-bindings'; import { IconButton } from '../Button'; export type CallRecordingListHeaderProps = { @@ -12,13 +13,16 @@ export const CallRecordingListHeader = ({ callRecordings, onRefresh, }: CallRecordingListHeaderProps) => { + const { t } = useI18n(); return (
- Call Recordings + {t('Call Recordings')} {callRecordings.length ? ({callRecordings.length}) : null}
- + {onRefresh && ( + + )}
); }; diff --git a/packages/react-sdk/src/components/CallRecordingList/CallRecordingListItem.tsx b/packages/react-sdk/src/components/CallRecordingList/CallRecordingListItem.tsx index ed92cde504..26351f3724 100644 --- a/packages/react-sdk/src/components/CallRecordingList/CallRecordingListItem.tsx +++ b/packages/react-sdk/src/components/CallRecordingList/CallRecordingListItem.tsx @@ -1,23 +1,31 @@ import clsx from 'clsx'; -import { ComponentProps, ForwardedRef, forwardRef } from 'react'; import { CallRecording } from '@stream-io/video-client'; -import { CopyToClipboardButtonWithPopup } from '../Button'; +import { Icon } from '../Icon'; export type CallRecordingListItemProps = { /** CallRecording object to represent */ recording: CallRecording; }; + +const dateFormat = (date: string) => { + const format = new Date(date); + return format.toTimeString().split(' ')[0]; +}; export const CallRecordingListItem = ({ recording, }: CallRecordingListItemProps) => { return ( -
-
-
- {new Date(recording.end_time).toLocaleString()} -
+
  • +
    + {recording.filename} +
    +
    + {dateFormat(recording.start_time)}
    -
    +
    + {dateFormat(recording.end_time)} +
    + -
    +
  • ); }; -const CopyUrlButton = forwardRef( - (props: ComponentProps<'button'>, ref: ForwardedRef) => { - return ( - - ); - }, -); diff --git a/packages/react-sdk/src/components/CallStats/CallStats.tsx b/packages/react-sdk/src/components/CallStats/CallStats.tsx index 1b3f02445b..ec9730f0d5 100644 --- a/packages/react-sdk/src/components/CallStats/CallStats.tsx +++ b/packages/react-sdk/src/components/CallStats/CallStats.tsx @@ -1,12 +1,52 @@ -import { useEffect, useRef, useState } from 'react'; +import { ReactNode, useEffect, useRef, useState } from 'react'; +import clsx from 'clsx'; import { AggregatedStatsReport, CallStatsReport, } from '@stream-io/video-client'; -import { useCallStateHooks } from '@stream-io/video-react-bindings'; +import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings'; + +import { useFloating, useHover, useInteractions } from '@floating-ui/react'; + import { CallStatsLatencyChart } from './CallStatsLatencyChart'; +import { Icon } from '../Icon'; + +export enum Statuses { + GOOD = 'Good', + OK = 'Ok', + BAD = 'Bad', +} +export type Status = Statuses.GOOD | Statuses.OK | Statuses.BAD; + +const statsStatus = ({ + value, + lowBound, + highBound, +}: { + value: number; + lowBound: number; + highBound: number; +}): Status => { + if (value <= lowBound) { + return Statuses.GOOD; + } + + if (value >= lowBound && value <= highBound) { + return Statuses.OK; + } + + if (value >= highBound) { + return Statuses.BAD; + } -export const CallStats = () => { + return Statuses.GOOD; +}; + +export const CallStats = (props: { + latencyLowBound?: number; + latencyHighBound?: number; +}) => { + const { latencyLowBound = 75, latencyHighBound = 400 } = props; const [latencyBuffer, setLatencyBuffer] = useState< Array<{ x: number; y: number }> >(() => { @@ -14,6 +54,7 @@ export const CallStats = () => { return Array.from({ length: 20 }, (_, i) => ({ x: now + i, y: 0 })); }); + const { t } = useI18n(); const [publishBitrate, setPublishBitrate] = useState('-'); const [subscribeBitrate, setSubscribeBitrate] = useState('-'); const previousStats = useRef(); @@ -49,27 +90,72 @@ export const CallStats = () => { previousStats.current = callStatsReport; }, [callStatsReport]); + const latencyComparison = { + lowBound: latencyLowBound, + highBound: latencyHighBound, + value: callStatsReport?.publisherStats.averageRoundTripTimeInMs || 0, + }; + return (
    {callStatsReport && ( <> -

    Call Latency

    - +
    +

    + + {t('Call Latency')} +

    +

    + {t( + 'Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.', + )} +

    +
    + +
    + +
    + +
    +

    + + {t('Call performance')} +

    +

    + {t( + 'Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.', + )} +

    +
    -

    Call performance

    { ); }; -export const StatCard = (props: { label: string; value: string }) => { - const { label, value } = props; +export const StatCardExplanation = (props: { description: string }) => { + const { description } = props; + const [isOpen, setIsOpen] = useState(false); + + const { refs, floatingStyles, context } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + }); + + const hover = useHover(context); + + const { getReferenceProps, getFloatingProps } = useInteractions([hover]); + + return ( + <> +
    + +
    + {isOpen && ( +
    + {description} +
    + )} + + ); +}; + +export const StatsTag = ({ + children, + status = Statuses.GOOD, +}: { + children: ReactNode; + status: Statuses.GOOD | Statuses.OK | Statuses.BAD; +}) => { + return ( +
    +
    {children}
    +
    + ); +}; + +export const StatCard = (props: { + label: string; + value: string | ReactNode; + description?: string; + comparison?: { value: number; highBound: number; lowBound: number }; +}) => { + const { label, value, description, comparison } = props; + + const { t } = useI18n(); + const status = comparison ? statsStatus(comparison) : undefined; + return (
    -
    {label}
    -
    {value}
    +
    +
    + {label} + {description && } +
    +
    {value}
    +
    + {comparison && status && {t(status)}}
    ); }; diff --git a/packages/react-sdk/src/components/CallStats/CallStatsLatencyChart.tsx b/packages/react-sdk/src/components/CallStats/CallStatsLatencyChart.tsx index 88cc8fbb26..8fd0a06fb8 100644 --- a/packages/react-sdk/src/components/CallStats/CallStatsLatencyChart.tsx +++ b/packages/react-sdk/src/components/CallStats/CallStatsLatencyChart.tsx @@ -1,57 +1,86 @@ -import { ResponsiveLine } from '@nivo/line'; +import { + CategoryScale, + Chart as ChartJS, + ChartData, + ChartOptions, + LinearScale, + LineElement, + PointElement, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; +import { useMemo } from 'react'; + +ChartJS.register(CategoryScale, LinearScale, LineElement, PointElement); export const CallStatsLatencyChart = (props: { values: Array<{ x: number; y: number }>; }) => { const { values } = props; let max = 0; - const data = values.map((point) => { - const { y } = point; - max = Math.max(max, y); - return point; - }); - return ( -
    - = { + labels: values.map((point) => { + const date = new Date(point.x * 1000); + return `${date.getHours()}:${date.getMinutes()}`; + }), + datasets: [ + { + data: values.map((point) => { + const { y } = point; + max = Math.max(max, y); + return point; + }), + borderColor: '#00e2a1', + backgroundColor: '#00e2a1', + }, + ], + }; + + const options = useMemo>(() => { + return { + maintainAspectRatio: false, + animation: { + duration: 0, + }, + elements: { + line: { + borderWidth: 1, + }, + point: { + radius: 2, + }, + }, + scales: { + y: { + position: 'right', + stacked: true, min: 0, - max: max < 220 ? 220 : max + 30, - nice: true, - }} - theme={{ - axis: { - ticks: { - text: { - fill: '#FCFCFD', - }, - line: { - stroke: '#FCFCFD', - }, - }, + max: Math.max(180, Math.ceil((max + 10) / 10) * 10), + grid: { + display: true, + color: '#979ca0', }, + ticks: { + stepSize: 30, + }, + }, + x: { grid: { - line: { - strokeWidth: 0.1, - }, + display: false, + }, + ticks: { + display: false, }, - }} + }, + }, + }; + }, [max]); + + return ( +
    +
    ); diff --git a/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx b/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx index 04ca451ea1..4ac0132fe3 100644 --- a/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx +++ b/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx @@ -1,10 +1,12 @@ import clsx from 'clsx'; -import { ChangeEventHandler } from 'react'; +import { ChangeEventHandler, useCallback } from 'react'; + +import { DropDownSelect, DropDownSelectOption } from '../DropdownSelect'; type DeviceSelectorOptionProps = { id: string; label: string; - name: string; + name?: string; selected?: boolean; value: string; disabled?: boolean; @@ -44,19 +46,23 @@ const DeviceSelectorOption = ({ ); }; -export const DeviceSelector = (props: { + +export type DeviceSelectorType = 'audioinput' | 'audiooutput' | 'videoinput'; + +const DeviceSelectorList = (props: { devices: MediaDeviceInfo[]; + type: DeviceSelectorType; selectedDeviceId?: string; - title: string; + title?: string; onChange?: (deviceId: string) => void; }) => { const { devices = [], selectedDeviceId: selectedDeviceFromProps, title, + type, onChange, } = props; - const inputGroupName = title.replace(' ', '-').toLowerCase(); // sometimes the browser (Chrome) will report the system-default device // with an id of 'default'. In case when it doesn't, we'll select the first @@ -71,14 +77,16 @@ export const DeviceSelector = (props: { return (
    -
    - {title} -
    + {title && ( +
    + {title} +
    + )} {!devices.length ? ( @@ -86,14 +94,14 @@ export const DeviceSelector = (props: { devices.map((device) => { return ( { onChange?.(e.target.value); }} - name={inputGroupName} + name={type} selected={ device.deviceId === selectedDeviceId || devices.length === 1 } @@ -104,3 +112,90 @@ export const DeviceSelector = (props: {
    ); }; + +const DeviceSelectorDropdown = (props: { + devices: MediaDeviceInfo[]; + selectedDeviceId?: string; + title?: string; + onChange?: (deviceId: string) => void; + visualType?: 'list' | 'dropdown'; + icon: string; + placeholder?: string; +}) => { + const { + devices = [], + selectedDeviceId: selectedDeviceFromProps, + title, + onChange, + icon, + } = props; + + // sometimes the browser (Chrome) will report the system-default device + // with an id of 'default'. In case when it doesn't, we'll select the first + // available device. + let selectedDeviceId = selectedDeviceFromProps; + if ( + devices.length > 0 && + !devices.find((d) => d.deviceId === selectedDeviceId) + ) { + selectedDeviceId = devices[0].deviceId; + } + + const selectedIndex = devices.findIndex( + (d) => d.deviceId === selectedDeviceId, + ); + + const handleSelect = useCallback( + (index: number) => { + onChange?.(devices[index].deviceId); + }, + [devices, onChange], + ); + + return ( +
    +
    + {title} +
    + + {devices.map((device) => { + return ( + + ); + })} + +
    + ); +}; + +export const DeviceSelector = (props: { + devices: MediaDeviceInfo[]; + icon: string; + type: DeviceSelectorType; + selectedDeviceId?: string; + title?: string; + onChange?: (deviceId: string) => void; + visualType?: 'list' | 'dropdown'; + placeholder?: string; +}) => { + const { visualType = 'list', icon, placeholder, ...rest } = props; + + if (visualType === 'list') { + return ; + } + return ( + + ); +}; diff --git a/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorAudio.tsx b/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorAudio.tsx index 511fcdb696..794a8032ba 100644 --- a/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorAudio.tsx +++ b/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorAudio.tsx @@ -1,14 +1,15 @@ -import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings'; +import { useCallStateHooks } from '@stream-io/video-react-bindings'; import { DeviceSelector } from './DeviceSelector'; export type DeviceSelectorAudioInputProps = { title?: string; + visualType?: 'list' | 'dropdown'; }; export const DeviceSelectorAudioInput = ({ title, + visualType, }: DeviceSelectorAudioInputProps) => { - const { t } = useI18n(); const { useMicrophoneState } = useCallStateHooks(); const { microphone, selectedDevice, devices } = useMicrophoneState(); @@ -16,22 +17,26 @@ export const DeviceSelectorAudioInput = ({ { await microphone.select(deviceId); }} - title={title || t('Select a Mic')} + title={title} + visualType={visualType} + icon="mic" /> ); }; export type DeviceSelectorAudioOutputProps = { title?: string; + visualType?: 'list' | 'dropdown'; }; export const DeviceSelectorAudioOutput = ({ title, + visualType, }: DeviceSelectorAudioOutputProps) => { - const { t } = useI18n(); const { useSpeakerState } = useCallStateHooks(); const { speaker, selectedDevice, devices, isDeviceSelectionSupported } = useSpeakerState(); @@ -41,11 +46,14 @@ export const DeviceSelectorAudioOutput = ({ return ( { speaker.select(deviceId); }} - title={title || t('Select Speakers')} + title={title} + visualType={visualType} + icon="speaker" /> ); }; diff --git a/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorVideo.tsx b/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorVideo.tsx index 109f8df4c5..a3334ce5d4 100644 --- a/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorVideo.tsx +++ b/packages/react-sdk/src/components/DeviceSettings/DeviceSelectorVideo.tsx @@ -1,23 +1,29 @@ import { DeviceSelector } from './DeviceSelector'; -import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings'; +import { useCallStateHooks } from '@stream-io/video-react-bindings'; export type DeviceSelectorVideoProps = { title?: string; + visualType?: 'list' | 'dropdown'; }; -export const DeviceSelectorVideo = ({ title }: DeviceSelectorVideoProps) => { - const { t } = useI18n(); +export const DeviceSelectorVideo = ({ + title, + visualType, +}: DeviceSelectorVideoProps) => { const { useCameraState } = useCallStateHooks(); const { camera, devices, selectedDevice } = useCameraState(); return ( { await camera.select(deviceId); }} - title={title || t('Select a Camera')} + title={title} + visualType={visualType} + icon="camera" /> ); }; diff --git a/packages/react-sdk/src/components/DeviceSettings/DeviceSettings.tsx b/packages/react-sdk/src/components/DeviceSettings/DeviceSettings.tsx index 96c35d1be2..e4af3ae71e 100644 --- a/packages/react-sdk/src/components/DeviceSettings/DeviceSettings.tsx +++ b/packages/react-sdk/src/components/DeviceSettings/DeviceSettings.tsx @@ -1,44 +1,56 @@ import { forwardRef } from 'react'; import { useI18n } from '@stream-io/video-react-bindings'; -import { MenuToggle, ToggleMenuButtonProps } from '../Menu'; +import clsx from 'clsx'; +import { MenuToggle, MenuVisualType, ToggleMenuButtonProps } from '../Menu'; import { DeviceSelectorAudioInput, DeviceSelectorAudioOutput, } from './DeviceSelectorAudio'; import { DeviceSelectorVideo } from './DeviceSelectorVideo'; import { IconButton } from '../Button'; -import clsx from 'clsx'; -export const DeviceSettings = () => { +export type DeviceSettingsProps = { + visualType?: MenuVisualType; +}; + +export const DeviceSettings = ({ + visualType = MenuVisualType.MENU, +}: DeviceSettingsProps) => { return ( - + ); }; -const Menu = () => ( -
    - - - -
    -); - -const ToggleMenuButton = forwardRef( - ({ menuShown }, ref) => { - const { t } = useI18n(); +const Menu = () => { + const { t } = useI18n(); + return ( +
    + + + +
    + ); +}; - return ( - - ); - }, -); +const ToggleDeviceSettingsMenuButton = forwardRef< + HTMLButtonElement, + ToggleMenuButtonProps +>(function ToggleDeviceSettingsMenuButton({ menuShown }, ref) { + const { t } = useI18n(); + return ( + + ); +}); diff --git a/packages/react-sdk/src/components/DropdownSelect/DropdownSelect.tsx b/packages/react-sdk/src/components/DropdownSelect/DropdownSelect.tsx new file mode 100644 index 0000000000..6323121543 --- /dev/null +++ b/packages/react-sdk/src/components/DropdownSelect/DropdownSelect.tsx @@ -0,0 +1,214 @@ +import { + createContext, + ReactElement, + ReactNode, + useCallback, + useContext, + useMemo, + useRef, + useState, +} from 'react'; +import clsx from 'clsx'; +import { + autoUpdate, + flip, + FloatingFocusManager, + FloatingList, + useClick, + useDismiss, + useFloating, + useInteractions, + useListItem, + useListNavigation, + useRole, + useTypeahead, +} from '@floating-ui/react'; + +import { Icon } from '../Icon'; + +interface SelectContextValue { + activeIndex: number | null; + selectedIndex: number | null; + getItemProps: ReturnType['getItemProps']; + handleSelect: (index: number | null) => void; +} + +const SelectContext = createContext( + {} as SelectContextValue, +); + +const Select = (props: { + children: ReactNode; + icon?: string; + defaultSelectedLabel: string; + defaultSelectedIndex: number; + handleSelect: (index: number) => void; +}) => { + const { + children, + icon, + defaultSelectedLabel, + defaultSelectedIndex, + handleSelect: handleSelectProp, + } = props; + const [isOpen, setIsOpen] = useState(false); + const [activeIndex, setActiveIndex] = useState(null); + const [selectedIndex, setSelectedIndex] = useState( + defaultSelectedIndex, + ); + const [selectedLabel, setSelectedLabel] = useState( + defaultSelectedLabel, + ); + + const { refs, context } = useFloating({ + placement: 'bottom-start', + open: isOpen, + onOpenChange: setIsOpen, + whileElementsMounted: autoUpdate, + middleware: [flip()], + }); + + const elementsRef = useRef>([]); + const labelsRef = useRef>([]); + + const handleSelect = useCallback( + (index: number | null) => { + setSelectedIndex(index); + handleSelectProp(index || 0); + setIsOpen(false); + if (index !== null) { + setSelectedLabel(labelsRef.current[index]); + } + }, + [handleSelectProp], + ); + + const handleTypeaheadMatch = (index: number | null) => { + if (isOpen) { + setActiveIndex(index); + } else { + handleSelect(index); + } + }; + + const listNav = useListNavigation(context, { + listRef: elementsRef, + activeIndex, + selectedIndex, + onNavigate: setActiveIndex, + }); + const typeahead = useTypeahead(context, { + listRef: labelsRef, + activeIndex, + selectedIndex, + onMatch: handleTypeaheadMatch, + }); + const click = useClick(context); + const dismiss = useDismiss(context); + const role = useRole(context, { role: 'listbox' }); + + const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions( + [listNav, typeahead, click, dismiss, role], + ); + + const selectContext = useMemo( + () => ({ + activeIndex, + selectedIndex, + getItemProps, + handleSelect, + }), + [activeIndex, selectedIndex, getItemProps, handleSelect], + ); + + return ( +
    +
    + + +
    + + {isOpen && ( + +
    + + {children} + +
    +
    + )} +
    +
    + ); +}; + +export type DropDownSelectOptionProps = { + label: string; + selected?: boolean; + icon: string; +}; + +export const DropDownSelectOption = (props: DropDownSelectOptionProps) => { + const { selected, label, icon } = props; + const { getItemProps, handleSelect } = useContext(SelectContext); + const { ref, index } = useListItem(); + return ( +
    handleSelect(index), + })} + > + + {label} +
    + ); +}; + +export const DropDownSelect = (props: { + icon?: string; + defaultSelectedLabel: string; + defaultSelectedIndex: number; + handleSelect: (index: number) => void; + children: + | ReactElement + | ReactElement[]; +}) => { + const { + children, + icon, + handleSelect, + defaultSelectedLabel, + defaultSelectedIndex, + } = props; + return ( + + ); +}; diff --git a/packages/react-sdk/src/components/DropdownSelect/index.ts b/packages/react-sdk/src/components/DropdownSelect/index.ts new file mode 100644 index 0000000000..5468ab79aa --- /dev/null +++ b/packages/react-sdk/src/components/DropdownSelect/index.ts @@ -0,0 +1 @@ +export * from './DropdownSelect'; diff --git a/packages/react-sdk/src/components/Icon/Icon.tsx b/packages/react-sdk/src/components/Icon/Icon.tsx index 4704b1f57a..c143ab7dff 100644 --- a/packages/react-sdk/src/components/Icon/Icon.tsx +++ b/packages/react-sdk/src/components/Icon/Icon.tsx @@ -2,10 +2,15 @@ import clsx from 'clsx'; export type IconProps = { icon: string; + className?: string; }; -export const Icon = ({ icon }: IconProps) => ( +export const Icon = ({ className, icon }: IconProps) => ( ); diff --git a/packages/react-sdk/src/components/Menu/GenericMenu.tsx b/packages/react-sdk/src/components/Menu/GenericMenu.tsx index 1cd4afcc8f..160a561089 100644 --- a/packages/react-sdk/src/components/Menu/GenericMenu.tsx +++ b/packages/react-sdk/src/components/Menu/GenericMenu.tsx @@ -1,7 +1,29 @@ -import { ComponentProps, PropsWithChildren } from 'react'; +import { ComponentProps, MouseEvent, PropsWithChildren, useRef } from 'react'; -export const GenericMenu = ({ children }: PropsWithChildren) => { - return
      {children}
    ; +export const GenericMenu = ({ + children, + onItemClick, +}: PropsWithChildren<{ + onItemClick?: (e: MouseEvent) => void; +}>) => { + const ref = useRef(null); + return ( +
      { + if ( + onItemClick && + e.target !== ref.current && + ref.current?.contains(e.target as Node) + ) { + onItemClick(e); + } + }} + > + {children} +
    + ); }; export const GenericMenuButtonItem = ({ diff --git a/packages/react-sdk/src/components/Menu/MenuToggle.tsx b/packages/react-sdk/src/components/Menu/MenuToggle.tsx index 08f13c1f80..2d2464a3fe 100644 --- a/packages/react-sdk/src/components/Menu/MenuToggle.tsx +++ b/packages/react-sdk/src/components/Menu/MenuToggle.tsx @@ -1,11 +1,20 @@ import { ComponentType, + createContext, + ForwardedRef, PropsWithChildren, + useContext, useEffect, + useMemo, useState, - ForwardedRef, } from 'react'; -import { Placement, Strategy } from '@floating-ui/react'; +import { + FloatingOverlay, + FloatingPortal, + Placement, + Strategy, + UseFloatingReturn, +} from '@floating-ui/react'; import { useFloatingUIPreset } from '../../hooks'; @@ -14,16 +23,66 @@ export type ToggleMenuButtonProps = { ref: ForwardedRef; }; +export enum MenuVisualType { + PORTAL = 'portal', + MENU = 'menu', +} + export type MenuToggleProps = PropsWithChildren<{ ToggleButton: ComponentType>; placement?: Placement; strategy?: Strategy; + offset?: number; + visualType?: MenuVisualType; }>; +export type MenuContextValue = { + close?: () => void; +}; + +/** + * Used to provide utility APIs to the components rendered inside the portal. + */ +const MenuContext = createContext({}); + +/** + * Access to the closes MenuContext. + */ +export const useMenuContext = (): MenuContextValue => { + return useContext(MenuContext); +}; + +const MenuPortal = ({ + children, + refs, +}: PropsWithChildren<{ + refs: UseFloatingReturn['refs']; +}>) => { + const portalId = useMemo( + () => `str-video-portal-${Math.random().toString(36).substring(2, 9)}`, + [], + ); + + return ( + <> +
    + + +
    + {children} +
    +
    +
    + + ); +}; + export const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolute', + offset, + visualType = MenuVisualType.MENU, children, }: MenuToggleProps) => { const [menuShown, setMenuShown] = useState(false); @@ -31,6 +90,7 @@ export const MenuToggle = ({ const { floating, domReference, refs, x, y } = useFloatingUIPreset({ placement, strategy, + offset, }); useEffect(() => { @@ -62,18 +122,23 @@ export const MenuToggle = ({ return ( <> {menuShown && ( -
    - {children} -
    + setMenuShown(false) }}> + {visualType === MenuVisualType.PORTAL ? ( + + ) : visualType === MenuVisualType.MENU ? ( +
    + ) : null} + )} diff --git a/packages/react-sdk/src/components/Notification/Notification.tsx b/packages/react-sdk/src/components/Notification/Notification.tsx index b5173a7b47..b927a75036 100644 --- a/packages/react-sdk/src/components/Notification/Notification.tsx +++ b/packages/react-sdk/src/components/Notification/Notification.tsx @@ -10,6 +10,7 @@ export type NotificationProps = { resetIsVisible?: () => void; placement?: Placement; iconClassName?: string | null; + close?: () => void; }; export const Notification = (props: PropsWithChildren) => { @@ -21,6 +22,7 @@ export const Notification = (props: PropsWithChildren) => { resetIsVisible, placement = 'top', iconClassName = 'str-video__notification__icon', + close, } = props; const { refs, x, y, strategy } = useFloatingUIPreset({ @@ -53,6 +55,12 @@ export const Notification = (props: PropsWithChildren) => { > {iconClassName && } {message} + {close ? ( + + ) : null}
    )} {children} diff --git a/packages/react-sdk/src/components/Notification/RecordingInProgressNotification.tsx b/packages/react-sdk/src/components/Notification/RecordingInProgressNotification.tsx new file mode 100644 index 0000000000..72b01e9921 --- /dev/null +++ b/packages/react-sdk/src/components/Notification/RecordingInProgressNotification.tsx @@ -0,0 +1,40 @@ +import { PropsWithChildren, useEffect, useState } from 'react'; +import { useI18n } from '@stream-io/video-react-bindings'; +import { useToggleCallRecording } from '../../hooks'; +import { Notification } from './Notification'; + +export type RecordingInProgressNotificationProps = { + text?: string; +}; + +export const RecordingInProgressNotification = ({ + children, + text, +}: PropsWithChildren) => { + const { t } = useI18n(); + const { isCallRecordingInProgress } = useToggleCallRecording(); + + const [isVisible, setVisible] = useState(false); + + const message = text ?? t('Recording in progress...'); + + useEffect(() => { + if (isCallRecordingInProgress) { + setVisible(true); + } else { + setVisible(false); + } + }, [isCallRecordingInProgress]); + + return ( + setVisible(false)} + > + {children} + + ); +}; diff --git a/packages/react-sdk/src/components/Notification/SpeakingWhileMutedNotification.tsx b/packages/react-sdk/src/components/Notification/SpeakingWhileMutedNotification.tsx index 32b15e6430..f43a60c8c6 100644 --- a/packages/react-sdk/src/components/Notification/SpeakingWhileMutedNotification.tsx +++ b/packages/react-sdk/src/components/Notification/SpeakingWhileMutedNotification.tsx @@ -1,4 +1,6 @@ import { PropsWithChildren } from 'react'; +import { Placement } from '@floating-ui/react'; + import { useCallStateHooks, useI18n } from '@stream-io/video-react-bindings'; import { Notification } from './Notification'; @@ -7,11 +9,13 @@ export type SpeakingWhileMutedNotificationProps = { * Text message displayed by the notification. */ text?: string; + placement?: Placement; }; export const SpeakingWhileMutedNotification = ({ children, text, + placement, }: PropsWithChildren) => { const { useMicrophoneState } = useCallStateHooks(); const { isSpeakingWhileMuted } = useMicrophoneState(); @@ -19,7 +23,11 @@ export const SpeakingWhileMutedNotification = ({ const message = text ?? t('You are muted. Unmute to speak.'); return ( - + {children} ); diff --git a/packages/react-sdk/src/components/Notification/index.ts b/packages/react-sdk/src/components/Notification/index.ts index 2f3ea7382d..0f5306c4b4 100644 --- a/packages/react-sdk/src/components/Notification/index.ts +++ b/packages/react-sdk/src/components/Notification/index.ts @@ -1,3 +1,4 @@ export * from './Notification'; export * from './PermissionNotification'; export * from './SpeakingWhileMutedNotification'; +export * from './RecordingInProgressNotification'; diff --git a/packages/react-sdk/src/components/Permissions/PermissionRequests.tsx b/packages/react-sdk/src/components/Permissions/PermissionRequests.tsx index 21229e8152..7d094d8864 100644 --- a/packages/react-sdk/src/components/Permissions/PermissionRequests.tsx +++ b/packages/react-sdk/src/components/Permissions/PermissionRequests.tsx @@ -138,7 +138,7 @@ export type PermissionRequestListProps = ComponentProps<'div'> & { export const PermissionRequestList = forwardRef< HTMLDivElement, PermissionRequestListProps ->((props, ref) => { +>(function PermissionRequestList(props, ref) { const { permissionRequests, handleUpdatePermission, ...rest } = props; const { t } = useI18n(); diff --git a/packages/react-sdk/src/components/Search/hooks/useSearch.ts b/packages/react-sdk/src/components/Search/hooks/useSearch.ts index 3eff413921..bf1484e679 100644 --- a/packages/react-sdk/src/components/Search/hooks/useSearch.ts +++ b/packages/react-sdk/src/components/Search/hooks/useSearch.ts @@ -25,7 +25,11 @@ export const useSearch = ({ const [searchQueryInProgress, setSearchQueryInProgress] = useState(false); useEffect(() => { - if (!searchQuery.length) return setSearchResults([]); + if (!searchQuery.length) { + setSearchQueryInProgress(false); + setSearchResults([]); + return; + } setSearchQueryInProgress(true); diff --git a/packages/react-sdk/src/components/index.ts b/packages/react-sdk/src/components/index.ts index c6f41bec5e..10d13e0dfb 100644 --- a/packages/react-sdk/src/components/index.ts +++ b/packages/react-sdk/src/components/index.ts @@ -3,9 +3,11 @@ export * from './Button'; export * from './CallControls'; export * from './CallParticipantsList'; export * from './CallPreview'; +export * from './CallStats'; export * from './CallRecordingList'; export * from './CallStats'; export * from './DeviceSettings'; +export * from './DropdownSelect'; export * from './Icon'; export * from './LoadingIndicator'; export * from './Menu'; diff --git a/packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx b/packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx index 611f3f9224..156e6284db 100644 --- a/packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +++ b/packages/react-sdk/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx @@ -1,4 +1,4 @@ -import { forwardRef } from 'react'; +import { ComponentType, forwardRef } from 'react'; import { Placement } from '@floating-ui/react'; import { SfuModels } from '@stream-io/video-client'; import { useCall, useI18n } from '@stream-io/video-react-bindings'; @@ -9,9 +9,9 @@ import { IconButton, MenuToggle, Notification, - ParticipantActionsContextMenu, ToggleMenuButtonProps, } from '../../../components'; +import { ParticipantActionsContextMenu as DefaultParticipantActionsContextMenu } from './ParticipantActionsContextMenu'; import { Reaction } from '../../../components/Reaction'; import { useParticipantViewContext } from './ParticipantViewContext'; @@ -28,10 +28,14 @@ export type DefaultParticipantViewUIProps = { * Option to show/hide menu button component */ showMenuButton?: boolean; + /** + * Custom component to render the context menu + */ + ParticipantActionsContextMenu?: ComponentType; }; const ToggleButton = forwardRef( - (props, ref) => { + function ToggleButton(props, ref) { return ; }, ); @@ -62,11 +66,11 @@ export const DefaultScreenShareOverlay = () => { export const DefaultParticipantViewUI = ({ indicatorsVisible = true, - menuPlacement = 'bottom-end', + menuPlacement = 'bottom-start', showMenuButton = true, + ParticipantActionsContextMenu = DefaultParticipantActionsContextMenu, }: DefaultParticipantViewUIProps) => { - const { participant, participantViewElement, trackType, videoElement } = - useParticipantViewContext(); + const { participant, trackType } = useParticipantViewContext(); const { publishedTracks } = participant; const hasScreenShare = publishedTracks.includes( @@ -94,11 +98,7 @@ export const DefaultParticipantViewUI = ({ placement={menuPlacement} ToggleButton={ToggleButton} > - + )} @@ -112,7 +112,6 @@ export const ParticipantDetails = ({ }: Pick) => { const { participant } = useParticipantViewContext(); const { - isDominantSpeaker, isLocalParticipant, connectionQuality, publishedTracks, @@ -133,50 +132,65 @@ export const ParticipantDetails = ({ const canUnpin = !!pin && pin.isLocalPin; return ( -
    - - {name || userId} - {indicatorsVisible && isDominantSpeaker && ( - - )} - {indicatorsVisible && ( - - {connectionQualityAsString && ( - - )} - - )} - {indicatorsVisible && !hasAudio && ( - - )} - {indicatorsVisible && !hasVideo && ( - - )} - {indicatorsVisible && canUnpin && ( - // TODO: remove this monstrosity once we have a proper design - call?.unpin(sessionId)} - style={{ cursor: 'pointer' }} - className="str-video__participant-details__name--pinned" - /> - )} - -
    + <> +
    + + {name || userId} + + {indicatorsVisible && !hasAudio && ( + + )} + {indicatorsVisible && !hasVideo && ( + + )} + {indicatorsVisible && canUnpin && ( + // TODO: remove this monstrosity once we have a proper design + call?.unpin(sessionId)} + className="str-video__participant-details__name--pinned" + /> + )} + {indicatorsVisible && } + +
    + {indicatorsVisible && ( + + {connectionQualityAsString && ( + + )} + + )} + + ); +}; + +export const SpeechIndicator = () => { + const { participant } = useParticipantViewContext(); + const { isSpeaking, isDominantSpeaker } = participant; + return ( + + + + + ); }; diff --git a/packages/react-sdk/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx b/packages/react-sdk/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx new file mode 100644 index 0000000000..e650599af4 --- /dev/null +++ b/packages/react-sdk/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx @@ -0,0 +1,241 @@ +import { useEffect, useState } from 'react'; +import { Restricted, useCall, useI18n } from '@stream-io/video-react-bindings'; +import { OwnCapability, SfuModels } from '@stream-io/video-client'; +import { useParticipantViewContext } from './ParticipantViewContext'; +import { + GenericMenu, + GenericMenuButtonItem, + Icon, + useMenuContext, +} from '../../../components'; + +export const ParticipantActionsContextMenu = () => { + const { participant, participantViewElement, videoElement } = + useParticipantViewContext(); + const [fullscreenModeOn, setFullscreenModeOn] = useState( + !!document.fullscreenElement, + ); + const [pictureInPictureElement, setPictureInPictureElement] = useState( + document.pictureInPictureElement, + ); + const call = useCall(); + const { t } = useI18n(); + + const { pin, publishedTracks, sessionId, userId } = participant; + + const hasAudio = publishedTracks.includes(SfuModels.TrackType.AUDIO); + const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO); + const hasScreenShare = publishedTracks.includes( + SfuModels.TrackType.SCREEN_SHARE, + ); + const hasScreenShareAudio = publishedTracks.includes( + SfuModels.TrackType.SCREEN_SHARE_AUDIO, + ); + + const blockUser = () => call?.blockUser(userId); + const muteAudio = () => call?.muteUser(userId, 'audio'); + const muteVideo = () => call?.muteUser(userId, 'video'); + const muteScreenShare = () => call?.muteUser(userId, 'screenshare'); + const muteScreenShareAudio = () => + call?.muteUser(userId, 'screenshare_audio'); + + const grantPermission = (permission: string) => () => { + call?.updateUserPermissions({ + user_id: userId, + grant_permissions: [permission], + }); + }; + + const revokePermission = (permission: string) => () => { + call?.updateUserPermissions({ + user_id: userId, + revoke_permissions: [permission], + }); + }; + + const toggleParticipantPin = () => { + if (pin) { + call?.unpin(sessionId); + } else { + call?.pin(sessionId); + } + }; + + const pinForEveryone = () => { + call + ?.pinForEveryone({ + user_id: userId, + session_id: sessionId, + }) + .catch((err) => { + console.error(`Failed to pin participant ${userId}`, err); + }); + }; + + const unpinForEveryone = () => { + call + ?.unpinForEveryone({ + user_id: userId, + session_id: sessionId, + }) + .catch((err) => { + console.error(`Failed to unpin participant ${userId}`, err); + }); + }; + + const toggleFullscreenMode = () => { + if (!fullscreenModeOn) { + return participantViewElement?.requestFullscreen().catch(console.error); + } + return document.exitFullscreen().catch(console.error); + }; + + useEffect(() => { + // handles the case when fullscreen mode is toggled externally, + // e.g., by pressing ESC key or some other keyboard shortcut + const handleFullscreenChange = () => { + setFullscreenModeOn(!!document.fullscreenElement); + }; + document.addEventListener('fullscreenchange', handleFullscreenChange); + return () => { + document.removeEventListener('fullscreenchange', handleFullscreenChange); + }; + }, []); + + useEffect(() => { + if (!videoElement) return; + + const handlePiP = () => { + setPictureInPictureElement(document.pictureInPictureElement); + }; + + videoElement.addEventListener('enterpictureinpicture', handlePiP); + videoElement.addEventListener('leavepictureinpicture', handlePiP); + + return () => { + videoElement.removeEventListener('enterpictureinpicture', handlePiP); + videoElement.removeEventListener('leavepictureinpicture', handlePiP); + }; + }, [videoElement]); + + const togglePictureInPicture = () => { + if (videoElement && pictureInPictureElement !== videoElement) { + return videoElement + .requestPictureInPicture() + .catch(console.error) as Promise; + } + + return document.exitPictureInPicture().catch(console.error); + }; + + const { close } = useMenuContext() || {}; + return ( + + + + {pin ? t('Unpin') : t('Pin')} + + + + + {t('Pin for everyone')} + + + + {t('Unpin for everyone')} + + + + + + {t('Block')} + + + + {hasVideo && ( + + + {t('Turn off video')} + + )} + {hasScreenShare && ( + + + {t('Turn off screen share')} + + )} + {hasAudio && ( + + + {t('Mute audio')} + + )} + {hasScreenShareAudio && ( + + + {t('Mute screen share audio')} + + )} + + {participantViewElement && ( + + {t('{{ direction }} fullscreen', { + direction: fullscreenModeOn ? t('Leave') : t('Enter'), + })} + + )} + {videoElement && document.pictureInPictureEnabled && ( + + {t('{{ direction }} picture-in-picture', { + direction: + pictureInPictureElement === videoElement + ? t('Leave') + : t('Enter'), + })} + + )} + + + {t('Allow audio')} + + + {t('Allow video')} + + + {t('Allow screen sharing')} + + + + {t('Disable audio')} + + + {t('Disable video')} + + + {t('Disable screen sharing')} + + + + ); +}; diff --git a/packages/react-sdk/src/core/components/ParticipantView/ParticipantView.tsx b/packages/react-sdk/src/core/components/ParticipantView/ParticipantView.tsx index 5cf8445ee9..9cdeb9f577 100644 --- a/packages/react-sdk/src/core/components/ParticipantView/ParticipantView.tsx +++ b/packages/react-sdk/src/core/components/ParticipantView/ParticipantView.tsx @@ -62,7 +62,7 @@ export type ParticipantViewProps = { } & Pick; export const ParticipantView = forwardRef( - ( + function ParticipantView( { participant, trackType = 'videoTrack', @@ -73,7 +73,7 @@ export const ParticipantView = forwardRef( ParticipantViewUI = DefaultParticipantViewUI as ComponentType, }, ref, - ) => { + ) { const { isLocalParticipant, isSpeaking, diff --git a/packages/react-sdk/src/core/components/ParticipantView/ParticipantViewContext.tsx b/packages/react-sdk/src/core/components/ParticipantView/ParticipantViewContext.tsx index 8e29ac5446..d425c95ce1 100644 --- a/packages/react-sdk/src/core/components/ParticipantView/ParticipantViewContext.tsx +++ b/packages/react-sdk/src/core/components/ParticipantView/ParticipantViewContext.tsx @@ -4,9 +4,9 @@ import { ParticipantViewProps } from './ParticipantView'; export type ParticipantViewContextValue = Required< Pick > & { - participantViewElement: HTMLDivElement | null; - videoElement: HTMLVideoElement | null; - videoPlaceholderElement: HTMLDivElement | null; + participantViewElement?: HTMLDivElement | null; + videoElement?: HTMLVideoElement | null; + videoPlaceholderElement?: HTMLDivElement | null; }; export const ParticipantViewContext = createContext< diff --git a/packages/react-sdk/src/core/components/ParticipantView/index.ts b/packages/react-sdk/src/core/components/ParticipantView/index.ts index 972cce4d75..bbf285cd6c 100644 --- a/packages/react-sdk/src/core/components/ParticipantView/index.ts +++ b/packages/react-sdk/src/core/components/ParticipantView/index.ts @@ -1,3 +1,4 @@ +export * from './ParticipantActionsContextMenu'; export * from './ParticipantView'; export * from './ParticipantViewContext'; export * from './DefaultParticipantViewUI'; diff --git a/packages/react-sdk/src/core/components/Video/BaseVideo.tsx b/packages/react-sdk/src/core/components/Video/BaseVideo.tsx index 2befee39b9..8d5f2ae5fc 100644 --- a/packages/react-sdk/src/core/components/Video/BaseVideo.tsx +++ b/packages/react-sdk/src/core/components/Video/BaseVideo.tsx @@ -12,7 +12,7 @@ export type BaseVideoProps = ComponentPropsWithRef<'video'> & { * (`srcObject`) to reactively handle stream changes */ export const BaseVideo = forwardRef( - ({ stream, ...rest }, ref) => { + function BaseVideo({ stream, ...rest }, ref) { const [videoElement, setVideoElement] = useState( null, ); diff --git a/packages/react-sdk/src/core/components/Video/DefaultVideoPlaceholder.tsx b/packages/react-sdk/src/core/components/Video/DefaultVideoPlaceholder.tsx index a210cc8989..af377572bd 100644 --- a/packages/react-sdk/src/core/components/Video/DefaultVideoPlaceholder.tsx +++ b/packages/react-sdk/src/core/components/Video/DefaultVideoPlaceholder.tsx @@ -9,7 +9,7 @@ export type VideoPlaceholderProps = { export const DefaultVideoPlaceholder = forwardRef< HTMLDivElement, VideoPlaceholderProps ->(({ participant, style }, ref) => { +>(function DefaultVideoPlaceholder({ participant, style }, ref) { const { t } = useI18n(); const [error, setError] = useState(false); const name = participant.name || participant.userId; @@ -17,11 +17,11 @@ export const DefaultVideoPlaceholder = forwardRef<
    {(!participant.image || error) && (name ? ( -
    -
    {name[0]}
    -
    + ) : ( -
    {t('Video is disabled')}
    +
    + {t('Video is disabled')} +
    ))} {participant.image && !error && ( ); }); + +const InitialsFallback = (props: { name: string }) => { + const { name } = props; + const initials = name + .split(' ') + .slice(0, 2) + .map((n) => n[0]) + .join(''); + return ( +
    + {initials} +
    + ); +}; diff --git a/packages/react-sdk/src/hooks/useFloatingUIPreset.ts b/packages/react-sdk/src/hooks/useFloatingUIPreset.ts index 44e3838c35..514ab3fd55 100644 --- a/packages/react-sdk/src/hooks/useFloatingUIPreset.ts +++ b/packages/react-sdk/src/hooks/useFloatingUIPreset.ts @@ -12,7 +12,8 @@ import { export const useFloatingUIPreset = ({ placement, strategy, -}: Pick) => { + offset: offsetInPx = 10, +}: Pick & { offset?: number }) => { const { refs, x, @@ -23,7 +24,7 @@ export const useFloatingUIPreset = ({ placement, strategy, middleware: [ - offset(10), + offset(offsetInPx), shift(), flip(), size({ diff --git a/packages/react-sdk/src/translations/en.json b/packages/react-sdk/src/translations/en.json index d2cf47ec5c..3930336560 100644 --- a/packages/react-sdk/src/translations/en.json +++ b/packages/react-sdk/src/translations/en.json @@ -24,6 +24,7 @@ "Waiting for recording to start...": "Waiting for recording to start...", "Record call": "Record call", "Reactions": "Reactions", + "Statistics": "Statistics", "You can now share your screen.": "You can now share your screen.", "Awaiting for an approval to share screen.": "Awaiting for an approval to share screen.", "You can no longer share your screen.": "You can no longer share your screen.", @@ -37,6 +38,8 @@ "Me": "Me", "Unknown": "Unknown", "Toggle device menu": "Toggle device menu", + "Call Recordings": "Call Recordings", + "Refresh": "Refresh", "You are presenting your screen": "You are presenting your screen", "Stop Screen Sharing": "Stop Screen Sharing", @@ -69,6 +72,8 @@ "Disable screen sharing": "Disable screen sharing", "Enter": "Enter", "Leave": "Leave", + "Leave call": "Leave call", + "End call for all": "End call for all", "{{ direction }} fullscreen": "{{ direction }} fullscreen", "{{ direction }} picture-in-picture": "{{ direction }} picture-in-picture", diff --git a/packages/styling/index.scss b/packages/styling/index.scss index 6cd92be0d1..307ffd0a82 100644 --- a/packages/styling/index.scss +++ b/packages/styling/index.scss @@ -13,6 +13,7 @@ @import 'src/CallRecodingList'; @import 'src/CallStats'; @import 'src/DeviceSettings'; +@import 'src/DropdownSelect'; @import 'src/Icon'; @import 'src/LoadingIndicator'; @import 'src/Menu'; diff --git a/packages/styling/package.json b/packages/styling/package.json index 0c2d9ae991..46ee9121f5 100644 --- a/packages/styling/package.json +++ b/packages/styling/package.json @@ -10,6 +10,6 @@ }, "devDependencies": { "rimraf": "^5.0.5", - "sass": "^1.69.3" + "sass": "^1.69.5" } } diff --git a/packages/styling/src/Button/Button-layout.scss b/packages/styling/src/Button/Button-layout.scss index 68dee2e495..be107c3d47 100644 --- a/packages/styling/src/Button/Button-layout.scss +++ b/packages/styling/src/Button/Button-layout.scss @@ -1,24 +1,67 @@ -.str-video__composite-button { +.str-video__button { display: flex; - flex-direction: column; align-items: center; - gap: 0.25rem; - height: 100%; + justify-content: center; + border-radius: var(--str-video__border-radius-md); + padding: var(--str-video__spacing-sm) var(--str-video__spacing-lg); + text-align: center; + min-width: 120px; + width: 100%; + font-size: var(--str-video__font-size-md); + font-weight: 500; + border: 1px solid transparent; + color: var(--str-video__text-color1); + + background-color: var(--str-video__button-primary-base); + + &:hover { + background-color: var(--str-video__button-primary-hover); + cursor: pointer; + } + + &__icon { + margin-right: var(--str-video__spacing-xs); + height: 16px; + background-color: var(--str-video__text-color1); + } + + &:disabled { + background-color: var(--str-video__button-default-disabled); + cursor: not-allowed; + } +} + +.str-video__composite-button { + &--caption { + display: flex; + align-items: center; + flex-direction: column; + gap: 0.25rem; + } .str-video__composite-button__button-group { display: flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem; - height: 38px; - .str-video__call-controls__button { - padding: 0; + .str-video__composite-button__button { + all: unset; + position: relative; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + height: calc(38px - 1rem); + padding: 0.5rem; + + &:hover { + cursor: pointer; + } } - .str-video__call-controls__button.str-video__menu-toggle-button { - height: 100%; - padding-inline: 2px; + .str-video__menu-toggle-button { + padding: 0; + margin-left: -30px; + margin-right: 8px; } .str-video__loading-indicator__icon { @@ -28,32 +71,30 @@ mask-size: 1.25rem; } } + + &.str-video__composite-button--menu .str-video__composite-button__button { + padding-right: 2.5rem; + } } .str-video__call-controls__button { padding: 0.5rem; - &--variant-danger, &--variant-success { + &--variant-danger, + &--variant-success { padding: 0.5rem 1rem; } - // FIXME: ask Jared for better styling &[disabled] { cursor: not-allowed; - opacity: 0.7; } - .str-video__icon { - &--caret-down { - width: 0.5rem; - height: 0.5rem; - } - - &--caret-up { - width: 0.5rem; - height: 0.5rem; - } + &:hover, + &:hover > *:hover { + cursor: pointer; + } + .str-video__icon { &--call-end { width: 1.5rem; height: 1.5rem; @@ -77,3 +118,7 @@ --str-video__participant-list-header__close-button--background-color ); } + +.str-video__call-recording-list-item__action-button { + border: none; +} diff --git a/packages/styling/src/Button/Button-theme.scss b/packages/styling/src/Button/Button-theme.scss index 15db9c05c6..b8b871e558 100644 --- a/packages/styling/src/Button/Button-theme.scss +++ b/packages/styling/src/Button/Button-theme.scss @@ -8,29 +8,91 @@ /* The background color of the component */ --str-video__composite-button__button-group--background-color: var( - --str-video__background-color1 + --str-video__button-primary-base + ); + + /* The active background color of the component */ + --str-video__composite-button__button-group-active--background-color: var( + --str-video__button-primary-active + ); + + --str-video__composite-button__button-group-active-primary--background-color: var( + --str-video__button-primary-active + ); + + --str-video__composite-button__button-group-active-secondary--background-color: var( + --str-video__button-secondary-active + ); + + --str-video__composite-button__button-group-active-secondary-hover--background-color: var( + --str-video__button-secondary-hover + ); + + /* The hover background color of the component */ + --str-video__composite-button__button-group-hover--background-color: var( + --str-video__button-default-hover ); /* The border radius used for the borders of the component */ --str-video__composite-button__button-group--border-radius: var( --str-video__border-radius-xs ); - - /* The background color of the component */ - --str-video__composite-button__button-group-active--background-color: var( - --str-video__background-color7 - ); } .str-video__composite-button__button-group { @include utils.component-layer-overrides('composite-button__button-group'); + background-color: var( + --str-video__composite-button__button-group--background-color + ); + border-radius: var(--str-video__border-radius-circle); + + .str-video__call-controls__button.str-video__menu-toggle-button { + background-color: var(--str-video__button-primary-base); - .str-video__call-controls__button { - background-color: transparent; + &:hover { + background-color: var(--str-video__button-default-hover); + } + + &--active { + background-color: var( + --str-video__composite-button__button-group-hover--background-color + ); + color: white; + + &:hover { + background-color: var( + --str-video__composite-button__button-group-hover--background-color + ); + } + + &:disabled { + background-color: var( + --str-video__composite-button__button-group-hover--background-color + ); + } + } } - .str-video__call-controls__button.str-video__menu-toggle-button { - background-color: var(--str-video__background-color1); + &.str-video__composite-button__button-group--active { + background-color: var( + --str-video__composite-button__button-group-active--background-color + ); + + &:hover { + background-color: var(--str-video__button-primary-hover); + } + } + + &.str-video__composite-button__button-group--active-primary { + background-color: var( + --str-video__composite-button__button-group-active-primary--background-color + ); + } + + &.str-video__composite-button__button-group--active-secondary { + background-color: var( + --str-video__composite-button__button-group-active-secondary--background-color + ); } } @@ -42,32 +104,50 @@ white-space: nowrap; } -.str-video__composite-button__button-group:hover, -.str-video__composite-button__button-group--active { +.str-video__composite-button__button-group:hover { background-color: var( - --str-video__composite-button__button-group-active--background-color + --str-video__composite-button__button-group-hover--background-color ); + + &.str-video__composite-button__button-group--active-secondary:hover { + background-color: var( + --str-video__composite-button__button-group-active-secondary-hover--background-color + ); + } + + .str-video__icon { + background-color: var(--str-video__icon-hover); + } } .str-video__call-controls__button { - border-radius: var(--str-video__border-radius-xs); + border-radius: var(--str-video__border-radius-circle); text-decoration: none; box-shadow: none; border: none; - background-color: var(--str-video__background-color1); + background-color: var(--str-video__button-primary-base); &:hover { text-decoration: none; background-color: var( - --str-video__composite-button__button-group-active--background-color + --str-video__composite-button__button-group-hover--background-color ); + + .str-video__icon { + background-color: var(--str-video__icon-hover); + } } &--variant-danger { - background-color: var(--str-video__danger-color); + background-color: var(--str-video__button-tertiary-base); + padding: 0.4rem; + + .str-video__icon { + background-color: white; + } &:hover { - background-color: var(--str-video__danger-color--accent); + background-color: var(--str-video__button-tertiary-hover); } } diff --git a/packages/styling/src/CallControls/CallControls-layout.scss b/packages/styling/src/CallControls/CallControls-layout.scss index d149799f86..f257a9fc3b 100644 --- a/packages/styling/src/CallControls/CallControls-layout.scss +++ b/packages/styling/src/CallControls/CallControls-layout.scss @@ -5,15 +5,65 @@ padding: 1rem 0; .str-video__reactions-menu { - background-color: var(--str-video__background-color1); + background-color: var(--str-video__base-color7); display: flex; - flex-direction: column; - padding: 10px; - border-radius: var(--str-video__border-radius-xs); + + padding: var(--str-video__spacing-sm); + gap: 0.5rem; + + border-radius: var(--str-video__border-radius-lg); + + &--horizontal { + flex-direction: row; + } + + &--vertical { + flex-direction: column; + } .str-video__reactions-menu__button { - font-size: 1.5rem; - background-color: var(--str-video__background-color1); + font-size: 1.2rem; + height: 38px; + width: 38px; + background-color: var(--str-video__button-primary-base); + border-radius: var(--str-video__border-radius-circle); + border: 1px solid transparent; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background-color: var(--str-video__button-primary-hover); + cursor: pointer; + } + } + } +} + +.str-video__no-media-permission { + position: absolute; + background-color: var(--str-video__alert-caution); + height: 12px; + width: 12px; + border-radius: var(--str-video__border-radius-circle); + display: flex; + justify-content: center; + align-items: center; + + font-size: var(--str-video__font-size-xxs); + color: var(--str-video__base-color4); + cursor: pointer; + font-weight: 600; + top: 0; + right: -3px; +} + +.str-video__composite-button.str-video__device-unavailable { + .str-video__composite-button__button-group { + background-color: var(--str-video__button-default-disabled); + + &:hover { + background-color: var(--str-video__button-default-hover); } } } diff --git a/packages/styling/src/CallControls/CancelCallButton.scss b/packages/styling/src/CallControls/CancelCallButton.scss new file mode 100644 index 0000000000..587059bfc9 --- /dev/null +++ b/packages/styling/src/CallControls/CancelCallButton.scss @@ -0,0 +1,114 @@ +.str-video__end-call { + &__confirmation { + background-color: var(--str-video__base-color7); + + border-radius: var(--str-video__border-radius-xl); + + padding: var(--str-video__spacing-sm); + gap: var(--str-video__spacing-md); + + display: flex; + flex-direction: column; + } + + &__leave, + &__end { + min-width: 211px; + color: var(--str-video__text-color1); + + &-icon { + background-color: var(--str-video__text-color1); + } + } + + &__leave { + background-color: var(--str-video__button-tertiary-base); + border: 1px solid transparent; + + &:hover { + background-color: var(--str-video__button-primary-hover); + } + } + + &__end { + background-color: var(--str-video__button-primary-base); + + &:hover { + background-color: var(--str-video__button-tertiary-hover); + } + } +} + +.str-video__end-call.str-video__end-call__leave-icon, +.str-video__icon.str-video__end-call__end-icon { + background-color: var(--str-video__text-color1); +} + +.str-video__end-recording__confirmation { + background-color: var(--str-video__base-color7); + border-radius: var(--str-video__border-radius-lg); + padding: var(--str-video__spacing-xl); + gap: var(--str-video__spacing-lg); + display: flex; + flex-direction: column; +} + +.str-video__end-recording__header { + display: flex; + flex-direction: row; + gap: var(--str-video__spacing-md); +} + +.str-video__end-recording__heading { + font-size: var(--str-video__font-size-lg); + font-weight: 600; + margin: 0; +} + +.str-video__end-recording__description { + margin: 0; + color: var(--str-video__text-color2); + font-size: var(--str-video__font-size-sm); +} + +.str-video__end-recording__actions { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: var(--str-video__spacing-md); + + .str-video__composite-button { + width: 100%; + gap: var(--str-video__spacing-md); + + &:first-of-type { + background-color: transparent; + + .str-video__composite-button__button-group { + background-color: transparent; + border: 1px solid var(--str-video__base-color2); + width: 100%; + justify-content: center; + + &:hover { + background-color: var(--str-video__button-default-hover); + } + } + } + .str-video__composite-button__button-group { + background-color: var(--str-video__button-tertiary-base); + width: 100%; + justify-content: center; + + .str-video__text-button { + display: flex; + align-items: center; + gap: 5px; + } + + &:hover { + background-color: var(--str-video__button-tertiary-hover); + } + } + } +} diff --git a/packages/styling/src/CallControls/index.scss b/packages/styling/src/CallControls/index.scss index 704475cd08..cc51b31678 100644 --- a/packages/styling/src/CallControls/index.scss +++ b/packages/styling/src/CallControls/index.scss @@ -1 +1,2 @@ -@import "CallControls-layout"; +@import 'CallControls-layout'; +@import 'CancelCallButton'; diff --git a/packages/styling/src/CallLayout/SpeakerLayout-layout.scss b/packages/styling/src/CallLayout/SpeakerLayout-layout.scss index 5f63120760..fbf5c38c05 100644 --- a/packages/styling/src/CallLayout/SpeakerLayout-layout.scss +++ b/packages/styling/src/CallLayout/SpeakerLayout-layout.scss @@ -13,6 +13,7 @@ $scope-name: 'str-video__speaker-layout'; width: 100%; gap: 1rem; padding-inline: 2px; // to see "speaking" outline + padding: 2px; .str-video__participant-view { aspect-ratio: 16/9; @@ -20,9 +21,9 @@ $scope-name: 'str-video__speaker-layout'; .str-video__speaker-layout__spotlight { display: flex; - align-items: center; justify-content: center; min-height: 0; + height: 100%; .str-video__participant-view--speaking:has( .str-video__video--screen-share @@ -31,7 +32,8 @@ $scope-name: 'str-video__speaker-layout'; } .str-video__video { - object-fit: contain; + height: 100%; + width: 100%; } .str-video__participant-details, @@ -131,3 +133,16 @@ $scope-name: 'str-video__speaker-layout'; } } } + +@media (min-width: 600px) { + .str-video__speaker-layout { + .str-video__speaker-layout__spotlight { + align-items: center; + height: auto; + + .str-video__video { + object-fit: contain; + } + } + } +} diff --git a/packages/styling/src/CallParticipantList/CallParticiantListingItem-layout.scss b/packages/styling/src/CallParticipantList/CallParticiantListingItem-layout.scss index a6a890f20b..eb8dcf1918 100644 --- a/packages/styling/src/CallParticipantList/CallParticiantListingItem-layout.scss +++ b/packages/styling/src/CallParticipantList/CallParticiantListingItem-layout.scss @@ -3,7 +3,7 @@ .str-video__participant-listing-item { display: flex; justify-content: space-between; - gap: 0.75rem; + gap: var(--str-video__spacing-sm); padding-block: 0.5rem; width: 100%; align-items: center; @@ -13,14 +13,20 @@ } .str-video__participant-listing-item__display-name { + flex: 1; @include utils.ellipsis-text(); } + .str-video__participant-avatar { + display: flex; + align-items: center; + gap: var(--str-video__spacing-sm); + } + .str-video__participant-listing-item__media-indicator-group { display: flex; align-items: center; - padding-inline: 0.5rem; - gap: 0.75rem; + gap: var(--str-video__spacing-sm); .str-video__participant-listing-item__icon { width: 0.75rem; diff --git a/packages/styling/src/CallParticipantList/CallParticipantList-layout.scss b/packages/styling/src/CallParticipantList/CallParticipantList-layout.scss index 73c706696e..c5999b4ff6 100644 --- a/packages/styling/src/CallParticipantList/CallParticipantList-layout.scss +++ b/packages/styling/src/CallParticipantList/CallParticipantList-layout.scss @@ -3,10 +3,6 @@ $scope-name: 'str-video__participant-list'; .str-video__participant-list { display: flex; flex-direction: column; - gap: 0.75rem; - height: 100%; - width: 100%; - padding: 0.75rem; .str-video__participant-list-header { display: flex; @@ -14,30 +10,31 @@ $scope-name: 'str-video__participant-list'; .str-video__participant-list-header__title { flex: 1; + font-size: var(--str-video__font-size-sm); } - .str-video__participant-list-header__close-button { - width: 1.75rem; - height: 1.75rem; - - .str-video__participant-list-header__close-button--icon { - display: block; - height: 1rem; - width: 1rem; + > span { + height: 15px; + width: 15px; } } } + .str-video__search-input__container { + margin-top: var(--str-video__spacing-md); + } + .str-video__participant-list__content-header { - display: flex; + display: none; align-items: center; gap: 0.5rem; + margin-top: var(--str-video__spacing-md); + justify-content: flex-end; .str-video__participant-list__content-header-title { display: flex; align-items: center; - justify-content: space-between; - flex-grow: 1; + justify-content: flex-end; } .str-video__call-controls__button { @@ -56,9 +53,10 @@ $scope-name: 'str-video__participant-list'; } .str-video__participant-list__content { - height: 0; + height: auto; flex-grow: 1; overflow-y: auto; + margin-top: var(--str-video__spacing-xl); } .str-video__participant-list__footer { @@ -89,7 +87,8 @@ $scope-name: 'str-video__participant-list'; display: flex; align-items: center; width: 100%; - padding-block: 0.5rem; + padding: var(--str-video__spacing-md); + font-size: var(--str-video__font-size-sm); } } diff --git a/packages/styling/src/CallParticipantList/CallParticipantList-theme.scss b/packages/styling/src/CallParticipantList/CallParticipantList-theme.scss index 00c321e503..e69de29bb2 100644 --- a/packages/styling/src/CallParticipantList/CallParticipantList-theme.scss +++ b/packages/styling/src/CallParticipantList/CallParticipantList-theme.scss @@ -1,86 +0,0 @@ -@use '../utils'; - -.str-video { - --str-video__participant-list--background-color: var( - --str-video__background-color1 - ); - --str-video__participant-list--border-radius: var( - --str-video__border-radius-xs - ); - - --str-video__participant-list-header--color: var(--str-video__text-color1); - - --str-video__participant-list-header--title-count__color: var( - --str-video__text-color3 - ); - - --str-video__participant-list-header__close-button--background-color: #121416; - --str-video__participant-list-header__close-button--border-radius: var( - --str-video__border-radius-xs - ); - - --str-video__participant-list-header__close-button-icon--background-color: #72767e; -} - -.str-video__participant-list { - @include utils.component-layer-overrides('participant-list'); - - .str-video__participant-list-header__title { - @include utils.component-layer-overrides('participant-list-header'); - font-size: 1rem; - line-height: 1.25rem; - } - - .str-video__participant-list-header__title-count { - @include utils.component-layer-overrides( - 'participant-list-header--title-count' - ); - } - - .str-video__participant-list-header__title-anonymous { - @include utils.component-layer-overrides( - 'participant-list-header--title-anonymous' - ); - } - - .str-video__participant-list-header__close-button { - @include utils.component-layer-overrides( - 'participant-list-header__close-button' - ); - - .str-video__participant-list-header__close-button--icon { - @include utils.apply-mask-image( - var(--str-video__icon--close), - var( - --str-video__participant-list-header__close-button-icon--background-color - ), - 0.75rem 0.75rem - ); - } - } - - .str-video__participant-list--empty { - font-size: 13px; - } - - .str-video__participant-list__footer { - display: flex; - justify-content: center; - padding: 0 0.75rem 0.75rem; - - .str-video__invite-link-button { - font-size: 13px; - color: var(--str-video__text-color4); - background-color: var(--str-video__background-color4); - border-radius: var(--str-video__border-radius-xl); - - .str-video__invite-participant-icon { - @include utils.apply-mask-image( - var(--str-video__icon--user-plus), - var(--str-video__text-color4), - 1rem - ); - } - } - } -} diff --git a/packages/styling/src/CallRecodingList/CallRecordingList-layout.scss b/packages/styling/src/CallRecodingList/CallRecordingList-layout.scss index 330a14bd20..7134b06631 100644 --- a/packages/styling/src/CallRecodingList/CallRecordingList-layout.scss +++ b/packages/styling/src/CallRecodingList/CallRecordingList-layout.scss @@ -1,105 +1,60 @@ -@use '../utils'; - -.str-video__call-recording-list { - padding: 0.625rem; // todo: unify menu root paddings - min-width: 300px; +.str-video__call-recording-list__list { + display: flex; + flex-direction: column; + list-style: none; + margin: 0; + padding: 0; + + &:first-of-type { + .str-video__call-recording-list__item { + font-weight: 600; + } + } } -.str-video__call-recording-list__header { +.str-video__call-recording-list__item { display: flex; + flex-direction: row; justify-content: space-between; - padding: 0rem 0.5rem 0.5rem; // todo: unify menu header paddings align-items: center; + font-size: var(--str-video__font-size-xs); + border-bottom: 1px solid var(--str-video__base-color2); + padding: var(--str-video__spacing-sm) 0; + gap: var(--str-video__spacing-sm); - .str-video__call-recording-list__title { - display: flex; - gap: 0.5rem; - } - - .str-video__call-controls__button { - .str-video__icon { - width: 1rem; - height: 1rem; - } + &:last-of-type { + border-bottom: none; } } +.str-video__call-recording-list__filename { + width: 200px; +} -.str-video__call-recording-list__listing { - padding-inline: 0.5rem; - max-height: 400px; - overflow-y: auto; - - &.str-video__call-recording-list__listing--empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding-block: 2rem; - - .str-video__call-recording-list__listing--icon-empty { - width: 3rem; - height: 3rem; - } - - .str-video__call-recording-list__listing--text-empty { - margin: 0; - } - } +.str-video__call-recording-list__time { + width: 100px; } -.str-video__call-recording-list-item { +.str-video__call-recording-list__download { display: flex; - justify-content: space-between; - overflow: hidden; - width: 100%; - padding-block: 0.125rem; - - .str-video__call-recording-list-item__info { - display: flex; - gap: 0.5rem; - } - - .str-video__call-recording-list-item__actions { - display: flex; - - .str-video__call-recording-list-item__action-button { - display: flex; - justify-content: center; - align-items: center; - padding: 0; - width: 1.5rem; - height: 1.5rem; - - .str-video__call-recording-list-item__action-button-icon { - display: block; - width: 1.5rem; - height: 1.5rem; - - &.str-video__copy-button--icon { - width: 1.25rem; - height: 1.25rem; - } - } - } - } + justify-content: flex-end; + width: 100px; } -.str-video__call-recording-list__listing { - .str-video__loading-indicator { - padding-block: 0.5rem; - flex-direction: row; - justify-content: flex-start; - gap: 0.5rem; - - .str-video__loading-indicator__icon.spinner { - height: 1rem; - width: 1rem; - mask-size: 1rem; - } +.str-video__call-recording-list__filename, +.str-video__call-recording-list__time { + text-overflow: ellipsis; + overflow: hidden; +} - .str-video__loading-indicator-text { - @include utils.ellipsis-text; +.str-video__call-recording-list-item__action-button--download { + .str-video__icon--download { + height: 32px; + width: 32px; + } + &:hover { + .str-video__icon--download { + cursor: pointer; + background-color: var(--str-video__brand-color1); } } } diff --git a/packages/styling/src/CallRecodingList/CallRecordingList-theme.scss b/packages/styling/src/CallRecodingList/CallRecordingList-theme.scss index a4c0d5763d..4403104cec 100644 --- a/packages/styling/src/CallRecodingList/CallRecordingList-theme.scss +++ b/packages/styling/src/CallRecodingList/CallRecordingList-theme.scss @@ -8,9 +8,9 @@ .str-video__call-controls__button--icon-call-recordings { @include utils.apply-mask-image( - var(--str-video__icon--film-roll), - var(--str-video__text-color1), - 2px, + var(--str-video__icon--film-roll), + var(--str-video__text-color1), + 2px ); } @@ -37,60 +37,14 @@ var(--str-video__icon--refresh), var(--str-video__text-color1), contain - ) + ); } } .str-video__call-recording-list__listing--icon-empty { @include utils.apply-mask-image( - var(--str-video__icon--film-roll), - var(--str-video__text-color1), - contain + var(--str-video__icon--film-roll), + var(--str-video__text-color1), + contain ); } - -.str-video__call-recording-list-item__actions { - .str-video__call-recording-list-item__action-button { - border-radius: 4px; - background-color: transparent; - - - &:hover { - background-color: var(--str-video__background-color7); - } - - &:active { - background-color: var(--str-video__background-color5); - transition: background-color 0.2s ease-out; - } - - .str-video__download-button--icon { - @include utils.apply-mask-image( - var(--str-video__icon--download), - var(--str-video__text-color1), - contain - ); - } - - .str-video__copy-button--icon { - @include utils.apply-mask-image( - var(--str-video__icon--copy), - var(--str-video__text-color1), - contain - ); - } - } -} - - -.str-video__call-recording-list__listing { - .str-video__loading-indicator { - .str-video__loading-indicator-text { - font-weight: 500; - font-size: 0.75rem; - line-height: 1rem; - } - } -} - - diff --git a/packages/styling/src/CallStats.scss b/packages/styling/src/CallStats.scss index dbceaf4a96..3fb43032a3 100644 --- a/packages/styling/src/CallStats.scss +++ b/packages/styling/src/CallStats.scss @@ -1,8 +1,8 @@ .str-video__call-stats { - background-color: var(--str-video__background-color1); - border-radius: var(--str-video__border-radius-xs); - padding: 0.75rem; - max-width: 400px; + border-radius: var(--str-video__border-radius-lg); + background-color: var(--str-video__base-color7); + padding: var(--str-video__spacing-md); + width: 100%; display: flex; flex-direction: column; gap: 0.75rem; @@ -11,6 +11,29 @@ margin: unset; } + .str-video__call-stats__header { + display: flex; + flex-direction: column; + margin-bottom: var(--str-video__spacing-md); + } + + .str-video__call-stats__heading { + display: flex; + align-items: center; + } + + .str-video__call-stats__icon { + margin-right: var(--str-video__spacing-sm); + height: 18px; + width: 18px; + } + + .str-video__call-stats__description { + font-size: var(--str-video__font-size-sm); + margin-bottom: var(--str-video__spacing-sm); + font-weight: 600; + } + .str-video__call-stats__card-container { --gap: 1rem; display: flex; @@ -20,26 +43,76 @@ } .str-video__call-stats__card { - background-color: var(--str-video__background-color2); + display: flex; + justify-content: space-between; + background-color: var(--str-video__background-color4); border-radius: var(--str-video__border-radius-xs); width: calc(50% - calc(var(--gap) / 2)); padding: 0.5rem 0.5rem; - .str-video__call-stats__card_label { + .str-video__call-stats__card-content { + display: flex; + flex-direction: column; + } + + .str-video__call-stats__card-label { + display: flex; color: var(--str-video__text-color2); font-size: 0.5625rem; font-weight: 500; text-transform: uppercase; } - .str-video__call-stats__card_value { + .str-video__call-stats__card-value { font-size: 1.0625rem; font-weight: 500; line-height: 1.25rem; } } + .str-video__call-explanation__icon { + height: 12px; + width: 12px; + margin-left: var(--str-video__spacing-xs); + + &:hover { + cursor: pointer; + } + } + + .str-video__call-explanation__description { + background-color: var(--str-video__base-color3); + padding: var(--str-video__spacing-xs); + font-size: var(--str-video__font-size-xs); + border-radius: var(--str-video__border-radius-xs); + } + .str-video__call-stats-line-chart-container { - height: 15vh; + position: relative; + margin: auto; + height: 170px; + width: 100%; + } + + .str-video__call-stats__tag { + border-radius: var(--str-video__border-radius-sm); + padding: var(--str-video__spacing-xs); + font-size: var(--str-video__font-size-sm); + font-weight: 600; + height: 30px; + + &--good { + color: var(--str-video__alert-success); + background-color: var(--str-video__alert-success-background); + } + &--ok { + color: var(--str-video__alert-caution); + background-color: var(--str-video__alert-caution-background); + } + + &--bad { + color: var(--str-video__alert-warning); + background-color: var(--str-video__alert-warming-background); + } } } diff --git a/packages/styling/src/DeviceSettings/DeviceSettings-layout.scss b/packages/styling/src/DeviceSettings/DeviceSettings-layout.scss index e112c4da40..7508635311 100644 --- a/packages/styling/src/DeviceSettings/DeviceSettings-layout.scss +++ b/packages/styling/src/DeviceSettings/DeviceSettings-layout.scss @@ -8,22 +8,87 @@ .str-video__device-settings { padding: 0.625rem; z-index: 1; + font-family: 'Inter'; +} + +.str-video__device-settings__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 16px 0px 16px; + margin-bottom: 33px; +} + +.str-video__icon.str-video__device-settings__settings-icon { + height: 24px; + width: 24px; + color: var(--str-video__base-color1); +} + +.str-video__device-settings__heading { + width: 100%; + justify-self: start; + margin: unset; + margin-left: 8px; + color: var(--str-video__base-color1); + font-size: 24; + font-weight: 500; + word-wrap: 'break-word'; +} + +.str-video__device-settings__close-button { + color: var(--str-video__base-color1); + background-color: transparent; } .str-video__device-settings__device-kind { - padding: 0.75rem 1rem; + background-color: var(--str-video__base-color7); + padding: var(--str-video__spacing-lg); + border-radius: var(--str-video__border-radius-lg); .str-video__device-settings__device-selector-title { + font-size: var(--str-video__font-size-xs); + line-height: 1.25rem; + padding-bottom: 0.5rem; + font-size: 13px; + margin-bottom: 8px; } + .str-video__option, .str-video__device-settings__option { display: flex; align-items: center; - gap: 0.625rem; - padding: 0.5rem; + border-radius: var(--str-video__border-radius-lg); + background-color: var(--str-video__button-primary-base); + padding: var(--str-video__spacing-xs) var(--str-video__spacing-md); + + gap: 8px; + + font-weight: 600; + font-size: var(--str-video__font-size-sm); - input[type="radio"] { + &:not(:first-of-type) { + margin-top: 8px; + } + + &__icon { + &--selected { + background-color: var(--str-video__brand-color1); + } + } + + &:hover { + background-color: var(--str-video__button-default-hover); + cursor: pointer; + } + + &--selected, + &--active { + background-color: var(--str-video__button-default-hover); + } + + input[type='radio'] { margin: 0; height: 1rem; width: 1rem; @@ -31,7 +96,7 @@ place-content: center; &::before { - content: ""; + content: ''; width: 0.5rem; height: 0.5rem; transform: scale(0); @@ -39,6 +104,10 @@ &:checked::before { transform: scale(1); } + + &:checked { + border-color: var(--str-video__primary-color); + } } } } diff --git a/packages/styling/src/DeviceSettings/DeviceSettings-theme.scss b/packages/styling/src/DeviceSettings/DeviceSettings-theme.scss deleted file mode 100644 index 6ef5b39add..0000000000 --- a/packages/styling/src/DeviceSettings/DeviceSettings-theme.scss +++ /dev/null @@ -1,100 +0,0 @@ -@use "../utils"; - -.str-video { - /* The font color applied inside the component */ - --str-video__menu-container--color: var(--str-video__text-color1); - - /* The background color of the component */ - --str-video__menu-container--background-color: var(--str-video__background-color1); - - /* The border radius used for the borders of the component */ - --str-video__menu-container--border-radius: var(--str-video__border-radius-xs); - - /* The background color of the component */ - --str-video__device-settings--background-color: var(--str-video__background-color1); - - /* The border radius used for the borders of the component */ - --str-video__device-settings--border-radius: var(--str-video__border-radius-xs); - - /* The box shadow applied to the component */ - --str-video__device-settings--box-shadow: 0 0 4px 1px var(--str-video__background-color2); -} - -.str-video__device-settings { - @include utils.component-layer-overrides('device-settings'); -} - -.str-video__menu-container { - @include utils.component-layer-overrides('menu-container'); -} - -.str-video__device-settings__device-kind { - font-size: 1rem; - - .str-video__device-settings__device-selector-title { - font-weight: 500; - font-size: 1rem; - line-height: 1.25rem; - } - - .str-video__device-settings__option { - color: var(--str-video__text-color3); - font-size: 1rem; - line-height: 1.25rem; - border-radius: var(--str-video__border-radius-xxs); - cursor: pointer; - transition: all 0.3s ease-out; - - &:hover, &.str-video__device-settings__option--selected { - color: var(--str-video__text-color1); - background-color: var(--str-video__background-color2); - } - - input[type="radio"] { - -webkit-appearance: none; - appearance: none; - /* For iOS < 15 to remove gradient background */ - background-color: currentColor; - font: inherit; - color: var(--str-video__text-color3); - border: 0.5rem solid var(--str-video__text-color3); - border-radius: var(--str-video__border-radius-circle); - - &:checked { - border-color: var(--str-video__primary-color); - } - - &::before { - border-radius: var(--str-video__border-radius-circle); - transition: 120ms transform ease-in-out; - } - &:checked::before { - box-shadow: inset 0.5rem 0.5rem var(--str-video__text-color1); - } - } - } -} - -.str-video__device-settings__button { - cursor: pointer; - border: none; - background-color: var(--str-video__background-color1); - - .str-video__call-controls__button--icon-device-settings { - @include utils.apply-mask-image( - var(--str-video__icon--settings), - var(--str-video__text-color1), - contain - ); - } - - &:hover, - &:active { - background-color: var(--str-video__background-color7); - border-radius: var(--str-video__border-radius-xs); - } -} - -.str-video__device-settings__button--active { - background-color: var(--str-video__background-color7); -} diff --git a/packages/styling/src/DeviceSettings/index.scss b/packages/styling/src/DeviceSettings/index.scss index 1dd09153d6..2d13ba3986 100644 --- a/packages/styling/src/DeviceSettings/index.scss +++ b/packages/styling/src/DeviceSettings/index.scss @@ -1,2 +1 @@ -@import "DeviceSettings-layout"; -@import "DeviceSettings-theme"; +@import 'DeviceSettings-layout'; diff --git a/packages/styling/src/DropdownSelect/DropdownSelect.scss b/packages/styling/src/DropdownSelect/DropdownSelect.scss new file mode 100644 index 0000000000..e1db972903 --- /dev/null +++ b/packages/styling/src/DropdownSelect/DropdownSelect.scss @@ -0,0 +1,104 @@ +.str-video { + &__dropdown { + position: relative; + &-selected { + display: flex; + align-items: center; + justify-content: space-between; + background-color: var(--str-video__base-color4); + border-radius: var(--str-video__border-radius-lg); + border: 1px solid var(--str-video__base-color6); + + &__label { + display: flex; + align-items: center; + font-weight: 600; + padding: var(--str-video__spacing-sm); + font-size: var(--str-video__font-size-xs); + } + + &__icon { + margin-right: var(--str-video__spacing-sm); + } + + &__chevron { + margin-right: var(--str-video__spacing-md); + } + + & > *:hover, + &:hover { + cursor: pointer; + } + + &:hover { + border: 1px solid var(--str-video__brand-color1); + } + } + + &-list { + display: flex; + flex-direction: column; + position: absolute; + z-index: 2; + width: 100%; + margin-top: var(--str-video__spacing-sm); + background-color: var(--str-video__base-color4); + border-radius: var(--str-video__border-radius-lg); + padding: var(--str-video__spacing-md); + + box-shadow: 0px 14px 34px rgba(0, 0, 0, 0.75); + } + + &-option { + display: flex; + align-items: center; + border-radius: var(--str-video__border-radius-lg); + + padding: 8px 16px; + margin-top: 8px; + gap: 8px; + + font-weight: 600; + font-size: var(--str-video__font-size-sm); + + &:hover { + background-color: var(--str-video__button-default-hover); + cursor: pointer; + } + + &--selected { + background-color: var(--str-video__button-primary-active); + color: var(--str-video__text-color1); + + &:hover { + background-color: var(--str-video__button-primary-hover); + } + } + } + + &-icon { + background-color: var(--str-video__icon-default); + + .str-video__dropdown__option:hover & { + background-color: var(--str-video__icon-hover); + } + + .str-video__dropdown-option--selected & { + background-color: var(--str-video__text-color1); + } + } + + &-label { + font-size: var(--str-video__font-size-xs); + .str-video__dropdown__option:hover & { + color: var(--str-video__base-color1); + } + } + } +} + +@media (min-width: 600px) { + .str-video__dropdown-selected__label { + font-size: var(--str-video__font-size-sm); + } +} diff --git a/packages/styling/src/DropdownSelect/index.scss b/packages/styling/src/DropdownSelect/index.scss new file mode 100644 index 0000000000..61153f314d --- /dev/null +++ b/packages/styling/src/DropdownSelect/index.scss @@ -0,0 +1 @@ +@import 'DropdownSelect'; diff --git a/packages/styling/src/Icon/Icon-theme.scss b/packages/styling/src/Icon/Icon-theme.scss index f30682e570..22fee65e4d 100644 --- a/packages/styling/src/Icon/Icon-theme.scss +++ b/packages/styling/src/Icon/Icon-theme.scss @@ -1,5 +1,5 @@ .str-video__icon { - background-color: var(--str-video__text-color1); + background-color: var(--str-video__base-color1); &--chat { mask-image: var(--str-video__icon--chat); @@ -11,10 +11,100 @@ mask-image: var(--str-video__icon--reactions); } + &--call-latency { + -webkit-mask-image: var(--str-video__icon--call-latency); + mask-image: var(--str-video__icon--call-latency); + } + + &--network-quality { + -webkit-mask-image: var(--str-video__icon--network-quality); + mask-image: var(--str-video__icon--network-quality); + } + + &--support-agent { + -webkit-mask-image: var(--str-video__icon--support-agent); + mask-image: var(--str-video__icon--support-agent); + } + &--recording-on { mask-image: var(--str-video__icon--recording-on); -webkit-mask-image: var(--str-video__icon--recording-on); - background-color: var(--str-video__danger-color); + } + + &--mediation { + mask-image: var(--str-video__icon--mediation); + -webkit-mask-image: var(--str-video__icon--mediation); + } + + &--copy { + mask-image: var(--str-video__icon--copy); + -webkit-mask-image: var(--str-video__icon--copy); + } + + &--caret-up { + mask-image: var(--str-video__icon--caret-up); + -webkit-mask-image: var(--str-video__icon--caret-up); + } + + &--download { + mask-image: var(--str-video__icon--download); + -webkit-mask-image: var(--str-video__icon--download); + } + + &--caret-down { + mask-image: var(--str-video__icon--caret-down); + -webkit-mask-image: var(--str-video__icon--caret-down); + transform: rotate(180deg); + } + + &--layout-speaker-bottom { + mask-image: var(--str-video__icon--layout-speaker-bottom); + -webkit-mask-image: var(--str-video__icon--layout-speaker-bottom); + } + + &--layout-speaker-top { + mask-image: var(--str-video__icon--layout-speaker-top); + -webkit-mask-image: var(--str-video__icon--layout-speaker-top); + } + + &--layout-speaker-left { + mask-image: var(--str-video__icon--layout-speaker-left); + -webkit-mask-image: var(--str-video__icon--layout-speaker-left); + } + + &--layout-speaker-one-on-one { + mask-image: var(--str-video__icon--layout-speaker-one-on-one); + -webkit-mask-image: var(--str-video__icon--layout-speaker-one-on-one); + } + + &--layout-speaker-live-stream { + mask-image: var(--str-video__icon--layout-speaker-live-stream); + -webkit-mask-image: var(--str-video__icon--layout-speaker-live-stream); + } + + &--layout-speaker-right { + mask-image: var(--str-video__icon--layout-speaker-right); + -webkit-mask-image: var(--str-video__icon--layout-speaker-right); + } + + &--layout { + mask-image: var(--str-video__icon--layout); + -webkit-mask-image: var(--str-video__icon--layout); + } + + &--folder { + mask-image: var(--str-video__icon--folder); + -webkit-mask-image: var(--str-video__icon--folder); + } + + &--paperclip { + mask-image: var(--str-video__icon--paperclip); + -webkit-mask-image: var(--str-video__icon--paperclip); + } + + &--chevron-right { + mask-image: var(--str-video__icon--chevron-right); + -webkit-mask-image: var(--str-video__icon--chevron-right); } &--recording-off { @@ -22,6 +112,72 @@ -webkit-mask-image: var(--str-video__icon--recording-off); } + &--camera-add { + mask-image: var(--str-video__icon--camera-add); + -webkit-mask-image: var(--str-video__icon--camera-add); + } + + &--person-add { + mask-image: var(--str-video__icon--person-add); + -webkit-mask-image: var(--str-video__icon--person-add); + } + + &--qr-code { + mask-image: var(--str-video__icon--qr-code); + -webkit-mask-image: var(--str-video__icon--qr-code); + } + + &--loading { + mask-image: var(--str-video__icon--loading); + -webkit-mask-image: var(--str-video__icon--loading); + + transform: rotate(360deg); + transition-duration: 1s; + transition-delay: now; + animation-timing-function: linear; + animation-iteration-count: infinite; + } + + &--login { + mask-image: var(--str-video__icon--login); + -webkit-mask-image: var(--str-video__icon--login); + } + + &--logout { + mask-image: var(--str-video__icon--logout); + -webkit-mask-image: var(--str-video__icon--logout); + } + + &--provider-google { + mask-image: var(--str-video__icon--provider-google); + -webkit-mask-image: var(--str-video__icon--provider-google); + } + + &--person-off { + mask-image: var(--str-video__icon--person-off); + -webkit-mask-image: var(--str-video__icon--person-off); + } + + &--verified { + mask-image: var(--str-video__icon--verified); + -webkit-mask-image: var(--str-video__icon--verified); + } + + &--chevron-up { + mask-image: var(--str-video__icon--chevron-up); + -webkit-mask-image: var(--str-video__icon--chevron-up); + } + + &--chevron-down { + mask-image: var(--str-video__icon--chevron-down); + -webkit-mask-image: var(--str-video__icon--chevron-down); + } + + &--developer { + mask-image: var(--str-video__icon--developer); + -webkit-mask-image: var(--str-video__icon--developer); + } + &--screen-share-on { mask-image: var(--str-video__icon--screen-share-on); -webkit-mask-image: var(--str-video__icon--screen-share-on); @@ -55,6 +211,11 @@ -webkit-mask-image: var(--str-video__icon--caret); } + &--star { + mask-image: var(--str-video__icon--star); + -webkit-mask-image: var(--str-video__icon--star); + } + &--close { mask-image: var(--str-video__icon--close); -webkit-mask-image: var(--str-video__icon--close); @@ -70,6 +231,11 @@ -webkit-mask-image: var(--str-video__icon--mic-off); } + &--more { + mask-image: var(--str-video__icon--more); + -webkit-mask-image: var(--str-video__icon--more); + } + &--camera { mask-image: var(--str-video__icon--camera); -webkit-mask-image: var(--str-video__icon--camera); @@ -100,6 +266,11 @@ mask-image: var(--str-video__icon--info-icon); } + &--feedback { + -webkit-mask-image: var(--str-video__icon--feedback); + mask-image: var(--str-video__icon--feedback); + } + &--info-document { -webkit-mask-image: var(--str-video__icon--info-document); mask-image: var(--str-video__icon--info-document); @@ -125,6 +296,11 @@ -webkit-mask-image: var(--str-video__icon--speaker); } + &--language { + mask-image: var(--str-video__icon--language); + -webkit-mask-image: var(--str-video__icon--language); + } + &--ellipsis { mask-image: var(--str-video__icon--ellipsis); -webkit-mask-image: var(--str-video__icon--ellipsis); @@ -150,7 +326,7 @@ -webkit-mask-image: var(--str-video__icon--not-allowed); } - &--call-recordings { + &--film-roll { mask-image: var(--str-video__icon--film-roll); -webkit-mask-image: var(--str-video__icon--film-roll); } @@ -165,8 +341,6 @@ -webkit-mask-image: var(--str-video__icon--filter); } - - &--refresh { mask-image: var(--str-video__icon--refresh); -webkit-mask-image: var(--str-video__icon--refresh); diff --git a/packages/styling/src/Menu/GenericMenu-layout.scss b/packages/styling/src/Menu/GenericMenu-layout.scss index 98b52ecc7e..75b7ce8871 100644 --- a/packages/styling/src/Menu/GenericMenu-layout.scss +++ b/packages/styling/src/Menu/GenericMenu-layout.scss @@ -1,20 +1,32 @@ .str-video__generic-menu { list-style: none; margin: unset; - padding: 5px; - border-radius: var(--str-video__border-radius-xs); + background-color: var(--str-video__base-color7); + padding: var(--str-video__spacing-md); + + border-radius: var(--str-video__border-radius-lg); + display: flex; flex-direction: column; gap: 1px; .str-video__generic-menu--item { + align-items: center; + + margin-top: 8px; + gap: 8px; + + font-weight: 600; + font-size: var(--str-video__font-size-md); + display: flex; button { all: unset; width: 100%; font-size: 13px; - padding: 5px 10px; - border-radius: var(--str-video__border-radius-xs); + border-radius: var(--str-video__border-radius-lg); + background-color: var(--str-video__button-primary-base); + padding: 8px 16px; gap: 10px; display: flex; align-items: center; @@ -24,6 +36,16 @@ width: 1.1rem; height: 1rem; } + + &:hover { + background-color: var(--str-video__button-default-hover); + cursor: pointer; + } + + &--selected, + &--active { + background-color: var(--str-video__button-primary-hover); + } } // TODO: remove once "device selector" component becomes reusable diff --git a/packages/styling/src/Menu/GenericMenu-theme.scss b/packages/styling/src/Menu/GenericMenu-theme.scss index 8445f8a972..e69de29bb2 100644 --- a/packages/styling/src/Menu/GenericMenu-theme.scss +++ b/packages/styling/src/Menu/GenericMenu-theme.scss @@ -1,15 +0,0 @@ -.str-video__generic-menu { - background: var(--str-video__background-color2); - - .str-video__generic-menu--item { - button:hover { - background: var(--str-video__background-color1); - } - - button[disabled] { - background-color: var(--str-video__background-color5); - color: var(--str-video__text-color2); - cursor: not-allowed; - } - } -} diff --git a/packages/styling/src/Menu/Menu-layout.scss b/packages/styling/src/Menu/Menu-layout.scss index 19a6cfc55e..74d5383a34 100644 --- a/packages/styling/src/Menu/Menu-layout.scss +++ b/packages/styling/src/Menu/Menu-layout.scss @@ -1,3 +1,9 @@ .str-video__menu-container { z-index: 2; + &::-webkit-scrollbar { + display: none; + } + + -ms-overflow-style: none; + scrollbar-width: none; } diff --git a/packages/styling/src/Menu/Menu-theme.scss b/packages/styling/src/Menu/Menu-theme.scss index 462fbb9051..2cb27a5fbd 100644 --- a/packages/styling/src/Menu/Menu-theme.scss +++ b/packages/styling/src/Menu/Menu-theme.scss @@ -1,3 +1,4 @@ .str-video__menu-container { - box-shadow: 0px 0px 5px var(--str-video__background-color1); + background-color: var(--str-video__base-color7); + border-radius: var(--str-video__border-radius-lg); } diff --git a/packages/styling/src/Menu/Portal-layout.scss b/packages/styling/src/Menu/Portal-layout.scss new file mode 100644 index 0000000000..9c482cac63 --- /dev/null +++ b/packages/styling/src/Menu/Portal-layout.scss @@ -0,0 +1,21 @@ +.str-video__portal { + position: fixed; + z-index: 1; + width: 100vw; + height: 100vh; + background-color: var(--str-video__backdrop1); + + left: 0; + top: 0; + + &:hover { + cursor: pointer; + } +} +.str-video__portal-content { + z-index: 2; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/packages/styling/src/Menu/index.scss b/packages/styling/src/Menu/index.scss index 1c8f875d28..c0bec613e7 100644 --- a/packages/styling/src/Menu/index.scss +++ b/packages/styling/src/Menu/index.scss @@ -1,4 +1,5 @@ -@import "Menu-layout"; -@import "Menu-theme"; -@import "GenericMenu-layout"; -@import "GenericMenu-theme"; \ No newline at end of file +@import 'Menu-layout'; +@import 'Portal-layout'; +@import 'Menu-theme'; +@import 'GenericMenu-layout'; +@import 'GenericMenu-theme'; diff --git a/packages/styling/src/Notification/Notification-layout.scss b/packages/styling/src/Notification/Notification-layout.scss index 240fada6cb..6f27cc4094 100644 --- a/packages/styling/src/Notification/Notification-layout.scss +++ b/packages/styling/src/Notification/Notification-layout.scss @@ -13,4 +13,14 @@ .str-video__notification__message { flex: 1; } + + .str-video__notification__close { + display: inline-block; + width: 1rem; + height: 1rem; + + &:hover { + cursor: pointer; + } + } } diff --git a/packages/styling/src/ParticipantView/ParticipantView-layout.scss b/packages/styling/src/ParticipantView/ParticipantView-layout.scss index 4282601048..81581ab99d 100644 --- a/packages/styling/src/ParticipantView/ParticipantView-layout.scss +++ b/packages/styling/src/ParticipantView/ParticipantView-layout.scss @@ -17,7 +17,6 @@ $scope-name: 'str-video__participant-view'; outline: 2px solid var(--str-video__primary-color); } - .str-video__participant-details, .str-video__call-controls__button { transition: opacity 200ms ease-out; opacity: 0.3; @@ -25,7 +24,6 @@ $scope-name: 'str-video__participant-view'; } &:hover { - .str-video__participant-details, .str-video__call-controls__button { opacity: 1; } @@ -34,28 +32,30 @@ $scope-name: 'str-video__participant-view'; & > .str-video__call-controls__button { position: absolute; top: 0.875rem; - right: 0.875rem; + left: 0.875rem; padding: 0.3rem; } .str-video__participant-details { position: absolute; - left: 0.875rem; - bottom: 0.875rem; + left: 0; + bottom: 0; display: flex; align-items: center; - gap: 0.3125rem; - border-radius: var(--str-video__border-radius-xs); + gap: var(--str-video__spacing-sm); + border-radius: 0 var(--str-video__border-radius-xs) 0 + var(--str-video__border-radius-sm); background-color: var(--str-video__background-color4); .str-video__participant-details__name { display: flex; align-items: center; - gap: 0.3125rem; + gap: var(--str-video__spacing-sm); padding: 4px 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-size: var(--str-video__font-size-sm); @mixin icon { $icon-size: 1rem; @@ -72,19 +72,6 @@ $scope-name: 'str-video__participant-view'; -webkit-mask-image: var(--str-video__icon--mic-off); } - .str-video__participant-details__name--dominant_speaker { - @include icon(); - mask-image: var(--str-video__icon--dominant-speaker); - -webkit-mask-image: var(--str-video__icon--dominant-speaker); - - mask-size: contain; - -webkit-mask-size: contain; - mask-repeat: no-repeat; - -webkit-mask-repeat: no-repeat; - mask-position: center; - -webkit-mask-position: center; - } - .str-video__participant-details__name--video-muted { @include icon(); mask-image: var(--str-video__icon--camera-off); @@ -92,6 +79,7 @@ $scope-name: 'str-video__participant-view'; } .str-video__participant-details__name--pinned { + cursor: pointer; @include icon(); width: 0.8rem; -webkit-mask-size: 0.8rem; @@ -112,22 +100,114 @@ $scope-name: 'str-video__participant-view'; margin-right: 0.625rem; } } + } - .str-video__participant-details__connection-quality { - width: 1.5rem; - height: 1.5rem; - display: block; - background-size: cover; + .str-video__participant-details__connection-quality { + position: absolute; + bottom: 0; + right: 0; + + border-radius: var(--str-video__border-radius-sm) 0 + var(--str-video__border-radius-sm) 0; + + width: 24px; + height: 24px; + display: block; + background-size: cover; + background-color: var(--str-video__background-color4); - &--poor { - background-image: var(--str-video__icon--connection-quality-poor); + &--poor { + background-image: var(--str-video__icon--connection-quality-poor); + } + &--good { + background-image: var(--str-video__icon--connection-quality-good); + } + &--excellent { + background-image: var(--str-video__icon--connection-quality-excellent); + } + } +} + +.str-video__speech-indicator { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1px; + height: 16px; + + .str-video__speech-indicator__bar { + width: 3px; + height: 20%; + background-color: var(--str-video__primary-color); + border-radius: 2px; + animation-duration: 0.4s; + animation-iteration-count: 1; + animation-name: speech-indicator-silence; + animation-timing-function: ease-in-out; + } + + &.str-video__speech-indicator--dominant { + .str-video__speech-indicator__bar { + background-color: var(--str-video__primary-color); + } + } + + &.str-video__speech-indicator--speaking { + .str-video__speech-indicator__bar { + height: 100%; + animation-iteration-count: infinite; + + &:nth-child(1) { + height: 70%; + animation-duration: 0.8s; + animation-name: speech-indicator-quiet; } - &--good { - background-image: var(--str-video__icon--connection-quality-good); + &:nth-child(2) { + height: 80%; + animation-duration: 1s; + animation-name: speech-indicator-loud; } - &--excellent { - background-image: var(--str-video__icon--connection-quality-excellent); + &:nth-child(3) { + height: 60%; + animation-duration: 0.9s; + animation-name: speech-indicator-quiet; } } } + + @keyframes speech-indicator-silence { + 25% { + transform: scaleY(2); + } + 50% { + transform: scaleY(2.5); + } + 75% { + transform: scaleY(1); + } + } + + @keyframes speech-indicator-quiet { + 25% { + transform: scaleY(0.5); + } + 50% { + transform: scaleY(0.3); + } + 75% { + transform: scaleY(0.5); + } + } + + @keyframes speech-indicator-loud { + 25% { + transform: scaleY(1); + } + 50% { + transform: scaleY(0.45); + } + 75% { + transform: scaleY(1.1); + } + } } diff --git a/packages/styling/src/Reaction/Reaction-layout.scss b/packages/styling/src/Reaction/Reaction-layout.scss index a7772a96ed..a9f8b79492 100644 --- a/packages/styling/src/Reaction/Reaction-layout.scss +++ b/packages/styling/src/Reaction/Reaction-layout.scss @@ -1,7 +1,7 @@ .str-video__reaction { position: absolute; right: 0.875rem; - bottom: 0.875rem; + top: 0.875rem; .str-video__reaction__emoji { display: flex; diff --git a/packages/styling/src/Search/SearchInput-theme.scss b/packages/styling/src/Search/SearchInput-theme.scss index 90c5b1ed17..f2334e49c8 100644 --- a/packages/styling/src/Search/SearchInput-theme.scss +++ b/packages/styling/src/Search/SearchInput-theme.scss @@ -47,6 +47,12 @@ } .str-video__search-input__clear-btn { background-color: transparent; + border-radius: var(--str-video__border-radius-circle); + border: none; + + &:hover { + cursor: pointer; + } .str-video__search-input__icon--active { @include utils.apply-mask-image( diff --git a/packages/styling/src/Tooltip/Tooltip-theme.scss b/packages/styling/src/Tooltip/Tooltip-theme.scss index ece99535e3..63c4aa9396 100644 --- a/packages/styling/src/Tooltip/Tooltip-theme.scss +++ b/packages/styling/src/Tooltip/Tooltip-theme.scss @@ -35,10 +35,3 @@ background-color: var(--str-video__tooltip-background-color); } } - -.str-video__copy-to-clipboard-button__popup { - color: var(--str-video__text-color1); - background-color: var(--str-video__primary-color); - font-size: 12px; - box-shadow: none; -} diff --git a/packages/styling/src/_base.scss b/packages/styling/src/_base.scss index 7b327cde13..a80cbcbcf4 100644 --- a/packages/styling/src/_base.scss +++ b/packages/styling/src/_base.scss @@ -4,25 +4,4 @@ *::after { box-sizing: border-box; } - - button { - cursor: pointer; - border-style: none; - box-shadow: none; - } -} - -.str-video { - .ngxp__container { - opacity: 1; - z-index: 1; - padding: 0; - border: none; - } - - .ngxp__container > .ngxp__arrow, - .ngxp__container > .ngxp__arrow:before { - width: 0; - height: 0; - } } diff --git a/packages/styling/src/_global-theme-variables.scss b/packages/styling/src/_global-theme-variables.scss index 6db798bea9..0b7126b112 100644 --- a/packages/styling/src/_global-theme-variables.scss +++ b/packages/styling/src/_global-theme-variables.scss @@ -1,13 +1,91 @@ .str-video { + // Brand colors + --str-video__brand-color1: #005fff; + --str-video__brand-color2: #69e5f6; + --str-video__brand-color3: #00e2a1; + --str-video__brand-color4: #ffd646; + --str-video__brand-color5: #dc433b; + --str_video__brand-color6: #b38af8; + + //Base colors + --str-video__base-color1: #e3e4e5; + --str-video__base-color2: #979ca0; + --str-video__base-color3: #4c535b; + --str-video__base-color4: #000000; + --str-video__base-color5: #0c0d0e; + --str-video__base-color6: #19232d; + --str-video__base-color7: #101213; + + //Backdrops + --str-video__backdrop1: rgba(0, 0, 0, 0.5); + + //Button colors: + --str-video__button-default-base: #19232d; + --str-video__button-default-hover: #4c535b; + --str-video__button-default-pressed: #0c0d0e; + --str-video__button-default-active: #19232d; + --str-video__button-default-disabled: #1e262e; + + --str-video__button-primary-base: #19232d; + --str-video__button-primary-hover: #4c8fff; + --str-video__button-primary-pressed: #0c48ab; + --str-video__button-primary-active: #005fff; + + --str-video__button-secondary-base: #19232d; + --str-video__button-secondary-hover: #e96962; + --str-video__button-secondary-pressed: #6a3233; + --str-video__button-secondary-active: #dc433b; + + --str-video__button-tertiary-base: #dc433b; + --str-video__button-tertiary-hover: #e96962; + --str-video__button-tertiary-pressed: #6a3233; + --str-video__button-tertiary-active: #31292f; + + //Icon colors: + --str-video__icon-default: #b0b4b7; + --str-video__icon-hover: #e3e4e5; + --str-video__icon-pressed: #656b72; + --str-video__icon-active: #005fff; + --str-video__icon-alert: #dc433b; + --str-video__icon-disabled: #323b44; + + //Alerts colors: + --str-video__alert-success: #00e2a1; + --str-video__alert-caution: #ffd646; + --str-video__alert-warning: #dc433b; + + //Alerts backgrounds: + --str-video__alert-success-background: rgba(0, 226, 161, 0.5); + --str-video__alert-caution-background: rgba(255, 214, 70, 0.5); + --str-video__alert-warning-background: rgba(220, 67, 59, 0.5); + + //Font sizes + --str-video__font-size-xxs: 0.625rem; + --str-video__font-size-xs: 0.75rem; + --str-video__font-size-sm: 0.875rem; + --str-video__font-size-md: 1rem; + --str-video__font-size-lg: 1.125rem; + --str-video__font-size-xl: 1.25rem; + --str-video__font-size-xxl: 1.5rem; + // Border raduiuses --str-video__border-radius-xxs: 4px; --str-video__border-radius-xs: 8px; --str-video__border-radius-sm: 12px; --str-video__border-radius-md: 18px; - --str-video__border-radius-lg: 20px; + --str-video__border-radius-lg: 24px; --str-video__border-radius-xl: 30px; + --str-video__border-radius-xxl: 48px; --str-video__border-radius-circle: 999px; + // spacing + --str-video__spacing-xxs: 4px; + --str-video__spacing-xs: 6px; + --str-video__spacing-sm: 8px; + --str-video__spacing-md: 12px; + --str-video__spacing-lg: 16px; + --str-video__spacing-xl: 20px; + // Colors // Main colors --str-video__primary-color: #005fff; @@ -31,7 +109,7 @@ --str-video__background-color2: #272a30; --str-video__background-color3: #f4f4f5; --str-video__background-color4: #121416; - --str-video__background-color5: #4a5059; + --str-video__background-color5: #1d2938; --str-video__background-color6: #b4b7bb; --str-video__background-color7: #72767e; --str-video__overlay-color: rgba(39, 42, 48, 0.75); @@ -39,44 +117,74 @@ --str-video__livestream-overlay-color-hovered: rgba(39, 42, 48, 0.5); // Icons - base64 encoded SVGs + --str-video__icon--call-accept: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkiIGhlaWdodD0iMTkiIHZpZXdCb3g9IjAgMCAxOSAxOSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xNy41MSAxMi4zODJDMTYuMjggMTIuMzgyIDE1LjA5IDEyLjE4MiAxMy45OCAxMS44MjJDMTMuNjMgMTEuNzAyIDEzLjI0IDExLjc5MiAxMi45NyAxMi4wNjJMMTEuNCAxNC4wMzJDOC41NyAxMi42ODIgNS45MiAxMC4xMzIgNC41MSA3LjIwMTk1TDYuNDYgNS41NDE5NUM2LjczIDUuMjYxOTUgNi44MSA0Ljg3MTk1IDYuNyA0LjUyMTk1QzYuMzMgMy40MTE5NSA2LjE0IDIuMjIxOTUgNi4xNCAwLjk5MTk1M0M2LjE0IDAuNDUxOTUzIDUuNjkgMC4wMDE5NTMxMyA1LjE1IDAuMDAxOTUzMTNIMS42OUMxLjE1IDAuMDAxOTUzMTIgMC41IDAuMjQxOTUzIDAuNSAwLjk5MTk1M0MwLjUgMTAuMjgyIDguMjMgMTguMDAyIDE3LjUxIDE4LjAwMkMxOC4yMiAxOC4wMDIgMTguNSAxNy4zNzIgMTguNSAxNi44MjJWMTMuMzcyQzE4LjUgMTIuODMyIDE4LjA1IDEyLjM4MiAxNy41MSAxMi4zODJaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); - --str-video__icon--call-end: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMTAiIHZpZXdCb3g9IjAgMCAyNiAxMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzIDIuNDY4NzRDMTEuMzMzMyAyLjQ2ODc0IDkuNzE4NzUgMi43MjkxNiA4LjIwODMzIDMuMjE4NzRWNi40NDc5MUM4LjIwODMzIDYuODU0MTYgNy45Njg3NSA3LjIxODc0IDcuNjI1IDcuMzg1NDFDNi42MDQxNyA3Ljg5NTgzIDUuNjc3MDggOC41NTIwOCA0Ljg1NDE3IDkuMzEyNDlDNC42NjY2NyA5LjQ5OTk5IDQuNDA2MjUgOS42MDQxNiA0LjEyNSA5LjYwNDE2QzMuODMzMzMgOS42MDQxNiAzLjU3MjkyIDkuNDg5NTggMy4zODU0MiA5LjMwMjA4TDAuODAyMDgzIDYuNzE4NzRDMC42MTQ1ODMgNi41NDE2NiAwLjUgNi4yODEyNCAwLjUgNS45ODk1N0MwLjUgNS42OTc5MSAwLjYxNDU4MyA1LjQzNzQ5IDAuODAyMDgzIDUuMjQ5OTlDMy45NzkxNyAyLjIzOTU3IDguMjcwODMgMC4zODU0MDYgMTMgMC4zODU0MDZDMTcuNzI5MiAwLjM4NTQwNiAyMi4wMjA4IDIuMjM5NTcgMjUuMTk3OSA1LjI0OTk5QzI1LjM4NTQgNS40Mzc0OSAyNS41IDUuNjk3OTEgMjUuNSA1Ljk4OTU3QzI1LjUgNi4yODEyNCAyNS4zODU0IDYuNTQxNjYgMjUuMTk3OSA2LjcyOTE2TDIyLjYxNDYgOS4zMTI0OUMyMi40MjcxIDkuNDk5OTkgMjIuMTY2NyA5LjYxNDU4IDIxLjg3NSA5LjYxNDU4QzIxLjU5MzggOS42MTQ1OCAyMS4zMzMzIDkuNDk5OTkgMjEuMTQ1OCA5LjMyMjkxQzIwLjMyMjkgOC41NTIwOCAxOS4zODU0IDcuOTA2MjQgMTguMzY0NiA3LjM5NTgyQzE4LjAyMDggNy4yMjkxNiAxNy43ODEyIDYuODc0OTkgMTcuNzgxMiA2LjQ1ODMyVjMuMjI5MTZDMTYuMjgxMiAyLjcyOTE2IDE0LjY2NjcgMi40Njg3NCAxMyAyLjQ2ODc0WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=='); - --str-video__icon--camera: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4LjExMTEgMTBWNS4zMzMzM0MxOC4xMTExIDQuNiAxNy41NjExIDQgMTYuODg4OSA0SDIuMjIyMjJDMS41NSA0IDEgNC42IDEgNS4zMzMzM1YxOC42NjY3QzEgMTkuNCAxLjU1IDIwIDIuMjIyMjIgMjBIMTYuODg4OUMxNy41NjExIDIwIDE4LjExMTEgMTkuNCAxOC4xMTExIDE4LjY2NjdWMTRMMjEuMjYyOCAxNy40MzgzQzIxLjg3OTEgMTguMTEwNSAyMyAxNy42NzQ1IDIzIDE2Ljc2MjVWNy4yMzc0N0MyMyA2LjMyNTQ2IDIxLjg3OTEgNS44ODk0NSAyMS4yNjI4IDYuNTYxNzRMMTguMTExMSAxMFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo='); - --str-video__icon--camera-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIzIDYuMjEwNTNMMTguMzY4NCAxMC44NDIxVjYuNzg5NDdDMTguMzY4NCA2LjE1MjYzIDE3Ljg0NzQgNS42MzE1OCAxNy4yMTA1IDUuNjMxNThIMTAuMDU0N0wyMyAxOC41NzY4VjYuMjEwNTNaTTIuNDcwNTMgMUwxIDIuNDcwNTNMNC4xNjEwNSA1LjYzMTU4SDMuMzE1NzlDMi42Nzg5NSA1LjYzMTU4IDIuMTU3ODkgNi4xNTI2MyAyLjE1Nzg5IDYuNzg5NDdWMTguMzY4NEMyLjE1Nzg5IDE5LjAwNTMgMi42Nzg5NSAxOS41MjYzIDMuMzE1NzkgMTkuNTI2M0gxNy4yMTA1QzE3LjQ1MzcgMTkuNTI2MyAxNy42NjIxIDE5LjQzMzcgMTcuODM1OCAxOS4zMTc5TDIxLjUyOTUgMjNMMjMgMjEuNTI5NUwyLjQ3MDUzIDFaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); - --str-video__icon--caret: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI1IiB2aWV3Qm94PSIwIDAgOSA1IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogICAgPHBhdGggZD0iTTQuNTAwMjQgMC43Njc3QzQuMzYzMjggMC43Njc3IDQuMjM4NCAwLjgyMDA2OCA0LjEzNzY5IDAuOTI4ODMzTDEuMDY4MTEgNC4wNjY4OUMwLjk3OTQ5MSA0LjE1NTUyIDAuOTM1MTggNC4yNjQyOCAwLjkzNTE4IDQuMzk3MjJDMC45MzUxOCA0LjY1OTA2IDEuMTQwNjIgNC44NjQ1IDEuMzk4NDQgNC44NjQ1QzEuNTI3MzQgNC44NjQ1IDEuNjQ4MTkgNC44MTIxMyAxLjczNjgyIDQuNzIzNTFMNC41MDAyNCAxLjg5MTZMNy4yNjM2NyA0LjcyMzUxQzcuMzU2MzIgNC44MTIxMyA3LjQ3MzE0IDQuODY0NSA3LjYwMjA1IDQuODY0NUM3Ljg2Mzg5IDQuODY0NSA4LjA2OTM0IDQuNjU5MDYgOC4wNjkzNCA0LjM5NzIyQzguMDY5MzQgNC4yNjgzMSA4LjAyMSA0LjE1NTUyIDcuOTMyMzcgNC4wNjY4OUw0Ljg2Mjc5IDAuOTI4ODMzQzQuNzU4MDYgMC44MjAwNjggNC42MzcyMSAwLjc2NzcgNC41MDAyNCAwLjc2NzdaIiBmaWxsPSIjNzI3NjdFIi8+Cjwvc3ZnPgo='); - --str-video__icon--chat: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNiAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIzIDMuMTk1MDdWMTcuNzI2M0g0LjQ2MjVMMyAxOS4xNDMxVjMuMTk1MDdIMjNaTTIzIDAuNzczMTkzSDNDMS42MjUgMC43NzMxOTMgMC41IDEuODYzMDQgMC41IDMuMTk1MDdWMjIuMDczNkMwLjUgMjMuMTUxMyAxLjg1IDIzLjY5NjIgMi42Mzc1IDIyLjkzMzNMNS41IDIwLjE0ODJIMjNDMjQuMzc1IDIwLjE0ODIgMjUuNSAxOS4wNTgzIDI1LjUgMTcuNzI2M1YzLjE5NTA3QzI1LjUgMS44NjMwNCAyNC4zNzUgMC43NzMxOTMgMjMgMC43NzMxOTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); - --str-video__icon--close: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOSIgdmlld0JveD0iMCAwIDEwIDkiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBkPSJNOS4xOTk0OSAwLjMwNTY3MUM4LjkzOTQ5IDAuMDQ1NjcwNyA4LjUxOTQ5IDAuMDQ1NjcwNyA4LjI1OTQ5IDAuMzA1NjcxTDQuOTk5NDkgMy41NTlMMS43Mzk0OSAwLjI5OTAwNEMxLjQ3OTQ5IDAuMDM5MDAzOSAxLjA1OTQ5IDAuMDM5MDAzOSAwLjc5OTQ5MiAwLjI5OTAwNEMwLjUzOTQ5MiAwLjU1OTAwNCAwLjUzOTQ5MiAwLjk3OTAwNCAwLjc5OTQ5MiAxLjIzOUw0LjA1OTQ5IDQuNDk5TDAuNzk5NDkyIDcuNzU5QzAuNTM5NDkyIDguMDE5IDAuNTM5NDkyIDguNDM5IDAuNzk5NDkyIDguNjk5QzEuMDU5NDkgOC45NTkgMS40Nzk0OSA4Ljk1OSAxLjczOTQ5IDguNjk5TDQuOTk5NDkgNS40MzlMOC4yNTk0OSA4LjY5OUM4LjUxOTQ5IDguOTU5IDguOTM5NDkgOC45NTkgOS4xOTk0OSA4LjY5OUM5LjQ1OTQ5IDguNDM5IDkuNDU5NDkgOC4wMTkgOS4xOTk0OSA3Ljc1OUw1LjkzOTQ5IDQuNDk5TDkuMTk5NDkgMS4yMzlDOS40NTI4MyAwLjk4NTY3MSA5LjQ1MjgzIDAuNTU5MDA0IDkuMTk5NDkgMC4zMDU2NzFaIiBmaWxsPSIjNzI3NjdFIi8+Cjwvc3ZnPgo='); - --str-video__icon--connection-quality-poor: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNSAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC40OTgwNDciIHk9IjAuNTAwOTc3IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSI2IiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+CjxwYXRoIGQ9Ik0xMi40OTggMTYuNTAxTDEyLjQ5OCAxMS41MDEiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik03LjQ5ODA1IDE2LjUwMUw3LjQ5ODA1IDE0LjUwMSIgc3Ryb2tlPSIjRkYzNzQyIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgLz4KPHBhdGggZD0iTTE3LjQ5OCAxNi41MDFMMTcuNDk4IDguNTAwOTgiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPgo8L3N2Zz4K'); - --str-video__icon--connection-quality-good: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNSAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC40OTgwNDciIHk9IjAuNDk5MDIzIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSI2IiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+CjxwYXRoIGQ9Ik0xMi40OTggMTYuNDk5TDEyLjQ5OCAxMS40OTkiIHN0cm9rZT0iIzAwNkNGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTcuNDk4MDUgMTYuNDk5TDcuNDk4MDUgMTQuNDk5IiBzdHJva2U9IiMwMDZDRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNy40OTggMTYuNDk5TDE3LjQ5OCA4LjQ5OTAyIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K'); - --str-video__icon--connection-quality-excellent: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNSAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMC40OTgwNDciIHk9IjAuNDk5MDIzIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSI2IiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+CjxwYXRoIGQ9Ik0xMi40OTggMTYuNDk5TDEyLjQ5OCAxMS40OTkiIHN0cm9rZT0iIzAwNkNGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTcuNDk4MDUgMTYuNDk5TDcuNDk4MDUgMTQuNDk5IiBzdHJva2U9IiMwMDZDRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNy40OTggMTYuNDk5TDE3LjQ5OCA4LjQ5OTAyIiBzdHJva2U9IiMwMDZDRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo='); - --str-video__icon--copy: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zLjUgMCAxOSAxOSI+PHBhdGggZD0iTTExLjQxNSA1LjA2OHY5LjE2YS40NjMuNDYzIDAgMCAxLS40NjIuNDYxSDkuNDc3djEuNDc3YS40NjMuNDYzIDAgMCAxLS40NjIuNDYySDEuMDQ3YS40NjMuNDYzIDAgMCAxLS40NjItLjQ2MlY0Ljk3NmEuNDYzLjQ2MyAwIDAgMSAuNDYyLS40NjJoMS40NzZWMy4wMzdhLjQ2My40NjMgMCAwIDEgLjQ2Mi0uNDYyaDUuOTM3ek04LjM2OSA3Ljg3NEg2LjYyOGEuNTI5LjUyOSAwIDAgMS0uNTI3LS41MjdWNS42MjJIMS42OTN2OS44OTdIOC4zN3ptLTQuNzM3LTQuMTl2LjgzaDMuMzUybDEuMDk1IDEuMDk1YS41MTguNTE4IDAgMCAxLS4wNC0uMlYzLjY4NHptNS44NDUgMy4zMjN2Ni41NzRoLjgzVjUuOTM2aC0xLjc0YS41MTcuNTE3IDAgMCAxLS4yLS4wNHoiLz48L3N2Zz4K'); - --str-video__icon--dominant-speaker: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAxNCAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuMDY4MzU5NCAxMi44NDE5QzAuMDY4MzU5NCAxMy4yMzIzIDAuMzczMDQ3IDEzLjUyNzQgMC43OTE5OTIgMTMuNTI3NEg2LjMxNDQ1TDMuNDEwNCAyMS4zMjU1QzMuMDIwMDIgMjIuMzYzNCA0LjA5NTk1IDIyLjkxNTYgNC43ODE0OSAyMi4wNzc3TDEzLjY3NDYgMTEuMDcwOUMxMy44NDU5IDEwLjg1MTkgMTMuOTMxNiAxMC42NTE5IDEzLjkzMTYgMTAuNDMzQzEzLjkzMTYgMTAuMDMzMSAxMy42MjcgOS43NDc0MSAxMy4yMDggOS43NDc0MUg3LjY4NTU1TDEwLjU4OTYgMS45Mzk3OUMxMC45OCAwLjkwMTk0NyA5LjkwNDA1IDAuMzQ5NzAxIDkuMjE4NTEgMS4xOTcxMUwwLjMzNDk2MSAxMi4yMDM5QzAuMTYzNTc0IDEyLjQxMzQgMC4wNjgzNTk0IDEyLjYxMzQgMC4wNjgzNTk0IDEyLjg0MTlaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); - --str-video__icon--download: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGQ9Ik01MTIgNjY2LjUgMzY3LjIgNTIxLjdsMzYuMi0zNi4yIDgzIDgzVjI1Nmg1MS4ydjMxMi41bDgzLTgzIDM2LjIgMzYuMkw1MTIgNjY2LjV6bS0yMDQuOCA1MC4zVjc2OGg0MDkuNnYtNTEuMkgzMDcuMnoiLz48L3N2Zz4K'); - --str-video__icon--ellipsis: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj4KCTxwYXRoCgkJZD0iTTM1MiAyNTZDMzUyIDIzOC4zIDM2Ni4zIDIyNCAzODQgMjI0QzQwMS43IDIyNCA0MTYgMjM4LjMgNDE2IDI1NkM0MTYgMjczLjcgNDAxLjcgMjg4IDM4NCAyODhDMzY2LjMgMjg4IDM1MiAyNzMuNyAzNTIgMjU2ek0xOTIgMjU2QzE5MiAyMzguMyAyMDYuMyAyMjQgMjI0IDIyNEMyNDEuNyAyMjQgMjU2IDIzOC4zIDI1NiAyNTZDMjU2IDI3My43IDI0MS43IDI4OCAyMjQgMjg4QzIwNi4zIDI4OCAxOTIgMjczLjcgMTkyIDI1NnpNOTYgMjU2Qzk2IDI3My43IDgxLjY3IDI4OCA2NCAyODhDNDYuMzMgMjg4IDMyIDI3My43IDMyIDI1NkMzMiAyMzguMyA0Ni4zMyAyMjQgNjQgMjI0QzgxLjY3IDIyNCA5NiAyMzguMyA5NiAyNTZ6IgoJLz4KPC9zdmc+Cg=='); - --str-video__icon--film-roll: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMzQzLjY1NiA0NTEuMTA5QzQxMCA0MTEuNDM4IDQ1NC40MjIgMzM4LjkwNiA0NTQuNDIyIDI1NmMwLTEyNS40ODQtMTAxLjcxOS0yMjcuMjE5LTIyNy4yMDMtMjI3LjIxOUMxMDEuNzE5IDI4Ljc4MSAwIDEzMC41MTYgMCAyNTZzMTAxLjcxOSAyMjcuMjE5IDIyNy4yMTkgMjI3LjIxOUg1MTJWNDUxLjExSDM0My42NTZ6bS0yNS4xNzItMzA1LjIzNGMyMy41NDctMTMuNTk0IDUzLjY0MS01LjUzMSA2Ny4yMzQgMTguMDE2czUuNTMxIDUzLjY1Ni0xOC4wMTYgNjcuMjVjLTIzLjU0NyAxMy41NzgtNTMuNjQxIDUuNTE2LTY3LjIzNC0xOC4wMTYtMTMuNjA5LTIzLjU2Mi01LjUzLTUzLjY1NiAxOC4wMTYtNjcuMjV6bS0xOC4wMzEgMTUxLjgxM2MxMy42MDktMjMuNTQ3IDQzLjcwMy0zMS42MDkgNjcuMjUtMTguMDE2IDIzLjU0NyAxMy42MDkgMzEuNjA5IDQzLjcwMyAxOC4wMTYgNjcuMjVzLTQzLjY4OCAzMS42MDktNjcuMjUgMTguMDE2Yy0yMy41MzEtMTMuNTk0LTMxLjYxLTQzLjcwNC0xOC4wMTYtNjcuMjV6TTIyNy4yMTkgNzIuMzc1YzI3LjE4OCAwIDQ5LjIxOSAyMi4wMzEgNDkuMjE5IDQ5LjIxOXMtMjIuMDMxIDQ5LjI1LTQ5LjIxOSA0OS4yNS00OS4yNS0yMi4wNjMtNDkuMjUtNDkuMjUgMjIuMDYyLTQ5LjIxOSA0OS4yNS00OS4yMTl6TTI0OS45MzggMjU2YzAgMTIuNTYzLTEwLjE3MiAyMi43MTktMjIuNzE5IDIyLjcxOS0xMi41NjMgMC0yMi43MTktMTAuMTU2LTIyLjcxOS0yMi43MTlzMTAuMTU2LTIyLjcxOSAyMi43MTktMjIuNzE5YzEyLjU0NyAwIDIyLjcxOSAxMC4xNTcgMjIuNzE5IDIyLjcxOXpNNjguNzAzIDE2My44OTFjMTMuNTk0LTIzLjU0NyA0My43MDMtMzEuNjA5IDY3LjI1LTE4LjAxNnMzMS42MDkgNDMuNjg4IDE4LjAxNiA2Ny4yNWMtMTMuNTk0IDIzLjUzMS00My43MDMgMzEuNjA5LTY3LjI1IDE4LjAxNi0yMy41MzEtMTMuNTk0LTMxLjYxLTQzLjcwMy0xOC4wMTYtNjcuMjV6bTY3LjI2NiAyMDEuMDQ3Yy0yMy41NjMgMTMuNTk0LTUzLjY1NiA1LjUzMS02Ny4yNjYtMTguMDE2LTEzLjU3OC0yMy41NDctNS41MTYtNTMuNjU2IDE4LjAxNi02Ny4yNjYgMjMuNTQ3LTEzLjU5NCA1My42NTYtNS41MTYgNjcuMjUgMTguMDMxczUuNTMxIDUzLjY1Ny0xOCA2Ny4yNTF6bTQyIDI0LjI2NWMwLTI3LjE4OCAyMi4wNjMtNDkuMjM0IDQ5LjI1LTQ5LjIzNHM0OS4yMTkgMjIuMDQ3IDQ5LjIxOSA0OS4yMzQtMjIuMDMxIDQ5LjIzNC00OS4yMTkgNDkuMjM0LTQ5LjI1LTIyLjA0Ni00OS4yNS00OS4yMzR6Ii8+PC9zdmc+Cg=='); + --str-video__icon--call-end: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTQuNTgwMSAxNi4wMzA0TDYuNTgwMSAxNC40NDA0QzcuMDYwMSAxNC4wNjA0IDcuMzQwMSAxMy40ODA0IDcuMzQwMSAxMi44NzA0VjEwLjI3MDRDMTAuMzYwMSA5LjI5MDQgMTMuNjMwMSA5LjI4MDQgMTYuNjYwMSAxMC4yNzA0VjEyLjg4MDRDMTYuNjYwMSAxMy40OTA0IDE2Ljk0MDEgMTQuMDcwNCAxNy40MjAxIDE0LjQ1MDRMMTkuNDEwMSAxNi4wMzA0QzIwLjIxMDEgMTYuNjYwNCAyMS4zNTAxIDE2LjYwMDQgMjIuMDcwMSAxNS44ODA0TDIzLjI5MDEgMTQuNjYwNEMyNC4wOTAxIDEzLjg2MDQgMjQuMDkwMSAxMi41MzA0IDIzLjI0MDEgMTEuNzgwNEMxNi44MzAxIDYuMTIwNCA3LjE3MDEgNi4xMjA0IDAuNzYwMTAzIDExLjc4MDRDLTAuMDg5ODk3IDEyLjUzMDQgLTAuMDg5ODk3IDEzLjg2MDQgMC43MTAxMDMgMTQuNjYwNEwxLjkzMDEgMTUuODgwNEMyLjY0MDEgMTYuNjAwNCAzLjc4MDEgMTYuNjYwNCA0LjU4MDEgMTYuMDMwNFoiIGZpbGw9IiNCMEI0QjciLz4KPC9nPgo8L3N2Zz4K'); + --str-video__icon--camera: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE3IDEwLjVWN0MxNyA2LjQ1IDE2LjU1IDYgMTYgNkg0QzMuNDUgNiAzIDYuNDUgMyA3VjE3QzMgMTcuNTUgMy40NSAxOCA0IDE4SDE2QzE2LjU1IDE4IDE3IDE3LjU1IDE3IDE3VjEzLjVMMTkuMjkgMTUuNzlDMTkuOTIgMTYuNDIgMjEgMTUuOTcgMjEgMTUuMDhWOC45MUMyMSA4LjAyIDE5LjkyIDcuNTcgMTkuMjkgOC4yTDE3IDEwLjVaIiBmaWxsPSIjQjBCNEI3Ii8+Cjwvc3ZnPgo='); + --str-video__icon--camera-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxLjI5MTUgMTQuNzY5OVY5LjQ3OTlDMjEuMjkxNSA4LjU4OTkgMjAuMjExNSA4LjEzOTkgMTkuNTgxNSA4Ljc2OTlMMTcuMjkxNSAxMS4wNjk5VjcuNTY5OUMxNy4yOTE1IDcuMDE5OSAxNi44NDE1IDYuNTY5OSAxNi4yOTE1IDYuNTY5OUgxMC42ODE1TDE5LjU5MTUgMTUuNDc5OUMyMC4yMTE1IDE2LjEwOTkgMjEuMjkxNSAxNS42NTk5IDIxLjI5MTUgMTQuNzY5OVpNMy4wMDE0OCAzLjEyOTlDMi42MTE0OCAzLjUxOTkgMi42MTE0OCA0LjE0OTkgMy4wMDE0OCA0LjUzOTlMNS4wMjE0OCA2LjU2OTlINC4yOTE0OEMzLjc0MTQ4IDYuNTY5OSAzLjI5MTQ4IDcuMDE5OSAzLjI5MTQ4IDcuNTY5OVYxNy41Njk5QzMuMjkxNDggMTguMTE5OSAzLjc0MTQ4IDE4LjU2OTkgNC4yOTE0OCAxOC41Njk5SDE2LjI5MTVDMTYuNTAxNSAxOC41Njk5IDE2LjY4MTUgMTguNDg5OSAxNi44NDE1IDE4LjM4OTlMMTkuMzIxNSAyMC44Njk5QzE5LjcxMTUgMjEuMjU5OSAyMC4zNDE1IDIxLjI1OTkgMjAuNzMxNSAyMC44Njk5QzIxLjEyMTUgMjAuNDc5OSAyMS4xMjE1IDE5Ljg0OTkgMjAuNzMxNSAxOS40NTk5TDQuNDExNDggMy4xMjk5QzQuMDIxNDggMi43Mzk5IDMuMzkxNDggMi43Mzk5IDMuMDAxNDggMy4xMjk5WiIgZmlsbD0iI0IwQjRCNyIvPgo8L3N2Zz4K'); + --str-video__icon--camera-add: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAyMiAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE1LjU4MzMgOS43MjU1OVY2LjUxNzI1QzE1LjU4MzMgNi4wMTMwOSAxNS4xNzA4IDUuNjAwNTkgMTQuNjY2NyA1LjYwMDU5SDMuNjY2NjdDMy4xNjI1IDUuNjAwNTkgMi43NSA2LjAxMzA5IDIuNzUgNi41MTcyNVYxNS42ODM5QzIuNzUgMTYuMTg4MSAzLjE2MjUgMTYuNjAwNiAzLjY2NjY3IDE2LjYwMDZIMTQuNjY2N0MxNS4xNzA4IDE2LjYwMDYgMTUuNTgzMyAxNi4xODgxIDE1LjU4MzMgMTUuNjgzOVYxMi40NzU2TDE3LjY4MjUgMTQuNTc0OEMxOC4yNiAxNS4xNTIzIDE5LjI1IDE0LjczOTggMTkuMjUgMTMuOTIzOVY4LjI2ODA5QzE5LjI1IDcuNDUyMjUgMTguMjYgNy4wMzk3NSAxNy42ODI1IDcuNjE3MjVMMTUuNTgzMyA5LjcyNTU5Wk0xMS45MTY3IDEyLjAxNzNIMTAuMDgzM1YxMy44NTA2QzEwLjA4MzMgMTQuMzU0OCA5LjY3MDgzIDE0Ljc2NzMgOS4xNjY2NyAxNC43NjczQzguNjYyNSAxNC43NjczIDguMjUgMTQuMzU0OCA4LjI1IDEzLjg1MDZWMTIuMDE3M0g2LjQxNjY3QzUuOTEyNSAxMi4wMTczIDUuNSAxMS42MDQ4IDUuNSAxMS4xMDA2QzUuNSAxMC41OTY0IDUuOTEyNSAxMC4xODM5IDYuNDE2NjcgMTAuMTgzOUg4LjI1VjguMzUwNTlDOC4yNSA3Ljg0NjQyIDguNjYyNSA3LjQzMzkyIDkuMTY2NjcgNy40MzM5MkM5LjY3MDgzIDcuNDMzOTIgMTAuMDgzMyA3Ljg0NjQyIDEwLjA4MzMgOC4zNTA1OVYxMC4xODM5SDExLjkxNjdDMTIuNDIwOCAxMC4xODM5IDEyLjgzMzMgMTAuNTk2NCAxMi44MzMzIDExLjEwMDZDMTIuODMzMyAxMS42MDQ4IDEyLjQyMDggMTIuMDE3MyAxMS45MTY3IDEyLjAxNzNaIiBmaWxsPSIjRkFGQUZBIi8+Cjwvc3ZnPgo='); + --str-video__icon--caret: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuMjU0ODggMTAuNDg4NUw5LjQxMzIxIDguMzMwMThDOS43MzgyMSA4LjAwNTE4IDEwLjI2MzIgOC4wMDUxOCAxMC41ODgyIDguMzMwMThMMTIuNzQ2NSAxMC40ODg1QzEzLjI3MTUgMTEuMDEzNSAxMi44OTY1IDExLjkxMzUgMTIuMTU0OSAxMS45MTM1SDcuODM4MjFDNy4wOTY1NCAxMS45MTM1IDYuNzI5ODggMTEuMDEzNSA3LjI1NDg4IDEwLjQ4ODVaIiBmaWxsPSIjRTNFNEU1Ii8+Cjwvc3ZnPgo='); + --str-video__icon--caret-down: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuMjU0ODggMTAuNDg4NUw5LjQxMzIxIDguMzMwMThDOS43MzgyMSA4LjAwNTE4IDEwLjI2MzIgOC4wMDUxOCAxMC41ODgyIDguMzMwMThMMTIuNzQ2NSAxMC40ODg1QzEzLjI3MTUgMTEuMDEzNSAxMi44OTY1IDExLjkxMzUgMTIuMTU0OSAxMS45MTM1SDcuODM4MjFDNy4wOTY1NCAxMS45MTM1IDYuNzI5ODggMTEuMDEzNSA3LjI1NDg4IDEwLjQ4ODVaIiBmaWxsPSIjRTNFNEU1Ii8+Cjwvc3ZnPgo='); + --str-video__icon--caret-up: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuMjU0ODggMTAuNDg4NUw5LjQxMzIxIDguMzMwMThDOS43MzgyMSA4LjAwNTE4IDEwLjI2MzIgOC4wMDUxOCAxMC41ODgyIDguMzMwMThMMTIuNzQ2NSAxMC40ODg1QzEzLjI3MTUgMTEuMDEzNSAxMi44OTY1IDExLjkxMzUgMTIuMTU0OSAxMS45MTM1SDcuODM4MjFDNy4wOTY1NCAxMS45MTM1IDYuNzI5ODggMTEuMDEzNSA3LjI1NDg4IDEwLjQ4ODVaIiBmaWxsPSIjRTNFNEU1Ii8+Cjwvc3ZnPgo='); + --str-video__icon--chat: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTIwIDZIMTlWMTRDMTkgMTQuNTUgMTguNTUgMTUgMTggMTVINlYxNkM2IDE3LjEgNi45IDE4IDggMThIMThMMjIgMjJWOEMyMiA2LjkgMjEuMSA2IDIwIDZaTTE3IDExVjRDMTcgMi45IDE2LjEgMiAxNSAySDRDMi45IDIgMiAyLjkgMiA0VjE3TDYgMTNIMTVDMTYuMSAxMyAxNyAxMi4xIDE3IDExWiIgZmlsbD0iI0IwQjRCNyIvPgo8L2c+Cjwvc3ZnPgo='); + --str-video__icon--chevron-down: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuNDEgOC4yOTUwNEwxMiAxMi44NzVMMTYuNTkgOC4yOTUwNEwxOCA5LjcwNTA0TDEyIDE1LjcwNUw2IDkuNzA1MDRMNy40MSA4LjI5NTA0WiIgZmlsbD0iI0IwQjRCNyIvPgo8L3N2Zz4K'); + --str-video__icon--chevron-up: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuNDEgMTUuNzA1TDEyIDExLjEyNUwxNi41OSAxNS43MDVMMTggMTQuMjk1TDEyIDguMjk1MDRMNiAxNC4yOTVMNy40MSAxNS43MDVaIiBmaWxsPSIjRTNFNEU1Ii8+Cjwvc3ZnPgo='); + --str-video__icon--chevron-right: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTYuOTk3NjcgMjEuMDE0OUM3LjQ4NzY3IDIxLjUwNDkgOC4yNzc2NyAyMS41MDQ5IDguNzY3NjcgMjEuMDE0OUwxNy4wNzc3IDEyLjcwNDlDMTcuNDY3NyAxMi4zMTQ5IDE3LjQ2NzcgMTEuNjg0OSAxNy4wNzc3IDExLjI5NDlMOC43Njc2NyAyLjk4NDkzQzguMjc3NjcgMi40OTQ5MyA3LjQ4NzY3IDIuNDk0OTMgNi45OTc2NyAyLjk4NDkzQzYuNTA3NjcgMy40NzQ5MyA2LjUwNzY3IDQuMjY0OTMgNi45OTc2NyA0Ljc1NDkzTDE0LjIzNzcgMTIuMDA0OUw2Ljk4NzY3IDE5LjI1NDlDNi41MDc2NyAxOS43MzQ5IDYuNTA3NjcgMjAuNTM0OSA2Ljk5NzY3IDIxLjAxNDlaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--call-latency: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyLjAwMjkgMTcuOTk4OEMxMi41NTUyIDE3Ljk5ODggMTMuMDAyOSAxNy41NTExIDEzLjAwMjkgMTYuOTk4OEMxMy4wMDI5IDE2LjQ0NjUgMTIuNTU1MiAxNS45OTg4IDEyLjAwMjkgMTUuOTk4OEMxMS40NTA2IDE1Ljk5ODggMTEuMDAyOSAxNi40NDY1IDExLjAwMjkgMTYuOTk4OEMxMS4wMDI5IDE3LjU1MTEgMTEuNDUwNiAxNy45OTg4IDEyLjAwMjkgMTcuOTk4OFoiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTcuMDAyOTMgMTIuOTk4OEM3LjU1NTIxIDEyLjk5ODggOC4wMDI5MyAxMi41NTExIDguMDAyOTMgMTEuOTk4OEM4LjAwMjkzIDExLjQ0NjUgNy41NTUyMSAxMC45OTg4IDcuMDAyOTMgMTAuOTk4OEM2LjQ1MDY0IDEwLjk5ODggNi4wMDI5MyAxMS40NDY1IDYuMDAyOTMgMTEuOTk4OEM2LjAwMjkzIDEyLjU1MTEgNi40NTA2NCAxMi45OTg4IDcuMDAyOTMgMTIuOTk4OFoiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTE3LjAwMjkgMTIuOTk4OEMxNy41NTUyIDEyLjk5ODggMTguMDAyOSAxMi41NTExIDE4LjAwMjkgMTEuOTk4OEMxOC4wMDI5IDExLjQ0NjUgMTcuNTU1MiAxMC45OTg4IDE3LjAwMjkgMTAuOTk4OEMxNi40NTA2IDEwLjk5ODggMTYuMDAyOSAxMS40NDY1IDE2LjAwMjkgMTEuOTk4OEMxNi4wMDI5IDEyLjU1MTEgMTYuNDUwNiAxMi45OTg4IDE3LjAwMjkgMTIuOTk4OFoiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTEyLjAwMjkgMi45OTg3OEMxMS40NTI5IDIuOTk4NzggMTEuMDAyOSAzLjQ0ODc4IDExLjAwMjkgMy45OTg3OFY1Ljk5ODc4QzExLjAwMjkgNi41NDg3OCAxMS40NTI5IDYuOTk4NzggMTIuMDAyOSA2Ljk5ODc4QzEyLjU1MjkgNi45OTg3OCAxMy4wMDI5IDYuNTQ4NzggMTMuMDAyOSA1Ljk5ODc4VjUuMDc4NzhDMTYuMzEyOSA1LjU1ODc4IDE4Ljg3MjkgOC4zMjg3OCAxOS4wMDI5IDExLjczODhDMTkuMTQyOSAxNS41ODg4IDE1Ljk3MjkgMTguOTM4OCAxMi4xMjI5IDE4Ljk5ODhDOC4xOTI5MyAxOS4wNTg4IDUuMDAyOTMgMTUuOTA4OCA1LjAwMjkzIDExLjk5ODhDNS4wMDI5MyAxMC4zMTg4IDUuNTkyOTMgOC43Nzg3OCA2LjU4MjkzIDcuNTc4NzhMMTEuMjkyOSAxMi4yOTg4QzExLjY4MjkgMTIuNjg4OCAxMi4zMTI5IDEyLjY4ODggMTIuNzAyOSAxMi4yOTg4QzEzLjA5MjkgMTEuOTA4OCAxMy4wOTI5IDExLjI3ODggMTIuNzAyOSAxMC44ODg4TDcuMjYyOTMgNS40NTg3OEM2Ljg4MjkzIDUuMDc4NzggNi4yNjI5MyA1LjA2ODc4IDUuODYyOTMgNS40Mzg3OEM0LjEwMjkzIDcuMDY4NzggMy4wMDI5MyA5LjM5ODc4IDMuMDAyOTMgMTEuOTk4OEMzLjAwMjkzIDE3LjAzODggNy4xNDI5MyAyMS4xMTg4IDEyLjIxMjkgMjAuOTk4OEMxNi45MTI5IDIwLjg4ODggMjAuODQyOSAxNi45ODg4IDIwLjk5MjkgMTIuMjg4OEMyMS4xNjI5IDcuMTg4NzggMTcuMDcyOSAyLjk5ODc4IDEyLjAwMjkgMi45OTg3OFoiIGZpbGw9IiNFM0U0RTUiLz4KPC9zdmc+Cg=='); + --str-video__icon--close: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTE4LjI5OTcgNS43MDk5N0MxNy45MDk3IDUuMzE5OTcgMTcuMjc5NyA1LjMxOTk3IDE2Ljg4OTcgNS43MDk5N0wxMS45OTk3IDEwLjU5TDcuMTA5NzMgNS42OTk5N0M2LjcxOTczIDUuMzA5OTcgNi4wODk3MyA1LjMwOTk3IDUuNjk5NzMgNS42OTk5N0M1LjMwOTczIDYuMDg5OTcgNS4zMDk3MyA2LjcxOTk3IDUuNjk5NzMgNy4xMDk5N0wxMC41ODk3IDEyTDUuNjk5NzMgMTYuODlDNS4zMDk3MyAxNy4yOCA1LjMwOTczIDE3LjkxIDUuNjk5NzMgMTguM0M2LjA4OTczIDE4LjY5IDYuNzE5NzMgMTguNjkgNy4xMDk3MyAxOC4zTDExLjk5OTcgMTMuNDFMMTYuODg5NyAxOC4zQzE3LjI3OTcgMTguNjkgMTcuOTA5NyAxOC42OSAxOC4yOTk3IDE4LjNDMTguNjg5NyAxNy45MSAxOC42ODk3IDE3LjI4IDE4LjI5OTcgMTYuODlMMTMuNDA5NyAxMkwxOC4yOTk3IDcuMTA5OTdDMTguNjc5NyA2LjcyOTk3IDE4LjY3OTcgNi4wODk5NyAxOC4yOTk3IDUuNzA5OTdaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--connection-quality-poor: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMyAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuMjU0ODgzIDYuMjVDMC4yNTQ4ODMgMi45MzYyOSAyLjk0MTE3IDAuMjUgNi4yNTQ4OCAwLjI1SDMyLjI1NDlWMjYuMjVDMzIuMjU0OSAyOS41NjM3IDI5LjU2ODYgMzIuMjUgMjYuMjU0OSAzMi4yNUgwLjI1NDg4M1Y2LjI1WiIgZmlsbD0iIzBDMEQwRSIgZmlsbC1vcGFjaXR5PSIwLjY1Ii8+CjxwYXRoIGQ9Ik0xNi4yNTQ5IDIwLjI1TDE2LjI1NDkgMTUuMjUiIHN0cm9rZT0iI0UzRTRFNSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTExLjI1NDkgMjAuMjVMMTEuMjU0OSAxOC4yNSIgc3Ryb2tlPSIjREM0MzNCIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjEuMjU0OSAyMC4yNUwyMS4yNTQ5IDEyLjI1IiBzdHJva2U9IiNFM0U0RTUiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo='); + --str-video__icon--connection-quality-good: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMyAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuOTI0ODA1IDYuMjVDMC45MjQ4MDUgMi45MzYyOSAzLjYxMTEgMC4yNSA2LjkyNDggMC4yNUgzMi45MjQ4VjI2LjI1QzMyLjkyNDggMjkuNTYzNyAzMC4yMzg1IDMyLjI1IDI2LjkyNDggMzIuMjVIMC45MjQ4MDVWNi4yNVoiIGZpbGw9IiMwQzBEMEUiIGZpbGwtb3BhY2l0eT0iMC42NSIvPgo8cGF0aCBkPSJNMTYuOTI0OCAyMC4yNUwxNi45MjQ4IDE1LjI1IiBzdHJva2U9IiMwMEUyQTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xMS45MjQ4IDIwLjI1TDExLjkyNDggMTguMjUiIHN0cm9rZT0iIzAwRTJBMSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTIxLjkyNDggMjAuMjVMMjEuOTI0OCAxMi4yNSIgc3Ryb2tlPSIjRTNFNEU1IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K'); + --str-video__icon--connection-quality-excellent: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMyAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuMjU0ODgzIDYuMjVDMC4yNTQ4ODMgMi45MzYyOSAyLjk0MTE3IDAuMjUgNi4yNTQ4OCAwLjI1SDMyLjI1NDlWMjYuMjVDMzIuMjU0OSAyOS41NjM3IDI5LjU2ODYgMzIuMjUgMjYuMjU0OSAzMi4yNUgwLjI1NDg4M1Y2LjI1WiIgZmlsbD0iIzBDMEQwRSIgZmlsbC1vcGFjaXR5PSIwLjY1Ii8+CjxwYXRoIGQ9Ik0xNi4yNTQ5IDIwLjI1TDE2LjI1NDkgMTUuMjUiIHN0cm9rZT0iIzAwRTJBMSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTExLjI1NDkgMjAuMjVMMTEuMjU0OSAxOC4yNSIgc3Ryb2tlPSIjMDBFMkExIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMjEuMjU0OSAyMC4yNUwyMS4yNTQ5IDEyLjI1IiBzdHJva2U9IiMwMEUyQTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo='); + --str-video__icon--copy: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNSAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9ImNvcHlfYWxsIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTE4Ljk2NDggMi40OTY1OEg5Ljk2NDg0QzguODY0ODQgMi40OTY1OCA3Ljk2NDg0IDMuMzk2NTggNy45NjQ4NCA0LjQ5NjU4VjE2LjQ5NjZDNy45NjQ4NCAxNy41OTY2IDguODY0ODQgMTguNDk2NiA5Ljk2NDg0IDE4LjQ5NjZIMTguOTY0OEMyMC4wNjQ4IDE4LjQ5NjYgMjAuOTY0OCAxNy41OTY2IDIwLjk2NDggMTYuNDk2NlY0LjQ5NjU4QzIwLjk2NDggMy4zOTY1OCAyMC4wNjQ4IDIuNDk2NTggMTguOTY0OCAyLjQ5NjU4Wk0xOC45NjQ4IDE2LjQ5NjZIOS45NjQ4NFY0LjQ5NjU4SDE4Ljk2NDhWMTYuNDk2NlpNMy45NjQ4NCAxNS40OTY2VjEzLjQ5NjZINS45NjQ4NFYxNS40OTY2SDMuOTY0ODRaTTMuOTY0ODQgOS45OTY1OEg1Ljk2NDg0VjExLjk5NjZIMy45NjQ4NFY5Ljk5NjU4Wk0xMC45NjQ4IDIwLjQ5NjZIMTIuOTY0OFYyMi40OTY2SDEwLjk2NDhWMjAuNDk2NlpNMy45NjQ4NCAxOC45OTY2VjE2Ljk5NjZINS45NjQ4NFYxOC45OTY2SDMuOTY0ODRaTTUuOTY0ODQgMjIuNDk2NkM0Ljg2NDg0IDIyLjQ5NjYgMy45NjQ4NCAyMS41OTY2IDMuOTY0ODQgMjAuNDk2Nkg1Ljk2NDg0VjIyLjQ5NjZaTTkuNDY0ODQgMjIuNDk2Nkg3LjQ2NDg0VjIwLjQ5NjZIOS40NjQ4NFYyMi40OTY2Wk0xNC40NjQ4IDIyLjQ5NjZWMjAuNDk2NkgxNi40NjQ4QzE2LjQ2NDggMjEuNTk2NiAxNS41NjQ4IDIyLjQ5NjYgMTQuNDY0OCAyMi40OTY2Wk01Ljk2NDg0IDYuNDk2NThWOC40OTY1OEgzLjk2NDg0QzMuOTY0ODQgNy4zOTY1OCA0Ljg2NDg0IDYuNDk2NTggNS45NjQ4NCA2LjQ5NjU4WiIgZmlsbD0iI0UzRTRFNSIvPgo8L2c+Cjwvc3ZnPgo='); + --str-video__icon--download: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAyOCAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE3LjA2MDcgNi4zMzM1SDE2LjAwMDdWMy4wMDAxNkMxNi4wMDA3IDIuNjMzNSAxNS43MDA3IDIuMzMzNSAxNS4zMzQgMi4zMzM1SDEyLjY2NzNDMTIuMzAwNyAyLjMzMzUgMTIuMDAwNyAyLjYzMzUgMTIuMDAwNyAzLjAwMDE2VjYuMzMzNUgxMC45NDA3QzEwLjM0NzMgNi4zMzM1IDEwLjA0NzMgNy4wNTM1IDEwLjQ2NzMgNy40NzM1TDEzLjUyNzMgMTAuNTMzNUMxMy43ODczIDEwLjc5MzUgMTQuMjA3MyAxMC43OTM1IDE0LjQ2NzMgMTAuNTMzNUwxNy41MjczIDcuNDczNUMxNy45NDczIDcuMDUzNSAxNy42NTQgNi4zMzM1IDE3LjA2MDcgNi4zMzM1Wk05LjMzMzk4IDEzLjAwMDJDOS4zMzM5OCAxMy4zNjY4IDkuNjMzOTggMTMuNjY2OCAxMC4wMDA3IDEzLjY2NjhIMTguMDAwN0MxOC4zNjczIDEzLjY2NjggMTguNjY3MyAxMy4zNjY4IDE4LjY2NzMgMTMuMDAwMkMxOC42NjczIDEyLjYzMzUgMTguMzY3MyAxMi4zMzM1IDE4LjAwMDcgMTIuMzMzNUgxMC4wMDA3QzkuNjMzOTggMTIuMzMzNSA5LjMzMzk4IDEyLjYzMzUgOS4zMzM5OCAxMy4wMDAyWiIgZmlsbD0iI0UzRTRFNSIvPgo8L3N2Zz4K'); + --str-video__icon--developer: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9ImNvZGUiPgo8cGF0aCBpZD0iVmVjdG9yIiBkPSJNNy4yNDkwOSAxMy4yNUwzLjk5OTA5IDEwTDcuMjQ5MDkgNi43NTAwMkM3LjU3NDA5IDYuNDI1MDIgNy41NzQwOSA1LjkwODM1IDcuMjQ5MDkgNS41ODMzNUM2LjkyNDA5IDUuMjU4MzUgNi40MDc0MiA1LjI1ODM1IDYuMDgyNDIgNS41ODMzNUwyLjI1NzQyIDkuNDA4MzVDMS45MzI0MiA5LjczMzM1IDEuOTMyNDIgMTAuMjU4MyAyLjI1NzQyIDEwLjU4MzNMNi4wODI0MiAxNC40MTY3QzYuNDA3NDIgMTQuNzQxNyA2LjkyNDA5IDE0Ljc0MTcgNy4yNDkwOSAxNC40MTY3QzcuNTc0MDkgMTQuMDkxNyA3LjU3NDA5IDEzLjU3NSA3LjI0OTA5IDEzLjI1Wk0xMi43NDkxIDEzLjI1TDE1Ljk5OTEgMTBMMTIuNzQ5MSA2Ljc1MDAyQzEyLjQyNDEgNi40MjUwMiAxMi40MjQxIDUuOTA4MzUgMTIuNzQ5MSA1LjU4MzM1QzEzLjA3NDEgNS4yNTgzNSAxMy41OTA4IDUuMjU4MzUgMTMuOTE1OCA1LjU4MzM1TDE3Ljc0MDggOS40MDgzNUMxOC4wNjU4IDkuNzMzMzUgMTguMDY1OCAxMC4yNTgzIDE3Ljc0MDggMTAuNTgzM0wxMy45MTU4IDE0LjQxNjdDMTMuNTkwOCAxNC43NDE3IDEzLjA3NDEgMTQuNzQxNyAxMi43NDkxIDE0LjQxNjdDMTIuNDI0MSAxNC4wOTE3IDEyLjQyNDEgMTMuNTc1IDEyLjc0OTEgMTMuMjVaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--ellipsis: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYgMTBDNC45IDEwIDQgMTAuOSA0IDEyQzQgMTMuMSA0LjkgMTQgNiAxNEM3LjEgMTQgOCAxMy4xIDggMTJDOCAxMC45IDcuMSAxMCA2IDEwWk0xOCAxMEMxNi45IDEwIDE2IDEwLjkgMTYgMTJDMTYgMTMuMSAxNi45IDE0IDE4IDE0QzE5LjEgMTQgMjAgMTMuMSAyMCAxMkMyMCAxMC45IDE5LjEgMTAgMTggMTBaTTEyIDEwQzEwLjkgMTAgMTAgMTAuOSAxMCAxMkMxMCAxMy4xIDEwLjkgMTQgMTIgMTRDMTMuMSAxNCAxNCAxMy4xIDE0IDEyQzE0IDEwLjkgMTMuMSAxMCAxMiAxMFoiIGZpbGw9IiNFM0U0RTUiLz4KPC9zdmc+Cg=='); + --str-video__icon--feedback: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTciIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNyAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzLjUwMDMgNy4xNjkzNkMxMy41MDAzIDcuNjI3NjkgMTMuODc1MyA4LjAwMjY5IDE0LjMzMzcgOC4wMDI2OUgxNi4wMDAzQzE2LjQ1ODcgOC4wMDI2OSAxNi44MzM3IDcuNjI3NjkgMTYuODMzNyA3LjE2OTM2QzE2LjgzMzcgNi43MTEwMyAxNi40NTg3IDYuMzM2MDMgMTYuMDAwMyA2LjMzNjAzSDE0LjMzMzdDMTMuODc1MyA2LjMzNjAzIDEzLjUwMDMgNi43MTEwMyAxMy41MDAzIDcuMTY5MzZaIiBmaWxsPSIjRTNFNEU1Ii8+CjxwYXRoIGQ9Ik0xMi4zMjUzIDExLjE4NkMxMi4wNTAzIDExLjU1MjcgMTIuMTI1MyAxMi4wNjEgMTIuNDkyIDEyLjMyNzdDMTIuOTMzNyAxMi42NTI3IDEzLjQwMDMgMTMuMDAyNyAxMy44NDIgMTMuMzM2QzE0LjIwODcgMTMuNjExIDE0LjcyNTMgMTMuNTM2IDE0Ljk5MiAxMy4xNjk0QzE0Ljk5MiAxMy4xNjEgMTUuMDAwMyAxMy4xNjEgMTUuMDAwMyAxMy4xNTI3QzE1LjI3NTMgMTIuNzg2IDE1LjIwMDMgMTIuMjY5NCAxNC44MzM3IDEyLjAwMjdDMTQuMzkyIDExLjY2OTQgMTMuOTI1MyAxMS4zMTk0IDEzLjQ5MiAxMC45OTQ0QzEzLjEyNTMgMTAuNzE5NCAxMi42MDg3IDEwLjgwMjcgMTIuMzMzNyAxMS4xNjk0QzEyLjMzMzcgMTEuMTc3NyAxMi4zMjUzIDExLjE4NiAxMi4zMjUzIDExLjE4NloiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTE1LjAwODcgMS4xNzc2OUMxNS4wMDg3IDEuMTY5MzYgMTUuMDAwMyAxLjE2OTM2IDE1LjAwMDMgMS4xNjEwM0MxNC43MjUzIDAuNzk0MzU5IDE0LjIwODcgMC43MTkzNTkgMTMuODUwMyAwLjk5NDM1OUMxMy40MDg3IDEuMzI3NjkgMTIuOTMzNyAxLjY3NzY5IDEyLjUwMDMgMi4wMTEwM0MxMi4xMzM3IDIuMjg2MDMgMTIuMDY3IDIuODAyNjkgMTIuMzQyIDMuMTYxMDNDMTIuMzQyIDMuMTY5MzYgMTIuMzUwMyAzLjE2OTM2IDEyLjM1MDMgMy4xNzc2OUMxMi42MjUzIDMuNTQ0MzYgMTMuMTMzNyAzLjYxOTM2IDEzLjUwMDMgMy4zNDQzNkMxMy45NDIgMy4wMTkzNiAxNC40MDg3IDIuNjYxMDMgMTQuODUwMyAyLjMyNzY5QzE1LjIwODcgMi4wNjEwMyAxNS4yNzUzIDEuNTQ0MzYgMTUuMDA4NyAxLjE3NzY5WiIgZmlsbD0iI0UzRTRFNSIvPgo8cGF0aCBkPSJNNS4xNjY5OSA0LjY2OTM2SDEuODMzNjZDMC45MTY5OTIgNC42NjkzNiAwLjE2Njk5MiA1LjQxOTM2IDAuMTY2OTkyIDYuMzM2MDNWOC4wMDI2OUMwLjE2Njk5MiA4LjkxOTM2IDAuOTE2OTkyIDkuNjY5MzYgMS44MzM2NiA5LjY2OTM2SDIuNjY2OTlWMTIuMTY5NEMyLjY2Njk5IDEyLjYyNzcgMy4wNDE5OSAxMy4wMDI3IDMuNTAwMzMgMTMuMDAyN0MzLjk1ODY2IDEzLjAwMjcgNC4zMzM2NiAxMi42Mjc3IDQuMzMzNjYgMTIuMTY5NFY5LjY2OTM2SDUuMTY2OTlMOS4zMzM2NiAxMi4xNjk0VjIuMTY5MzZMNS4xNjY5OSA0LjY2OTM2WiIgZmlsbD0iI0UzRTRFNSIvPgo8cGF0aCBkPSJNMTEuNDE3IDcuMTY5MzZDMTEuNDE3IDYuMDYxMDMgMTAuOTMzNyA1LjA2MTAzIDEwLjE2NyA0LjM3NzY5VjkuOTUyNjlDMTAuOTMzNyA5LjI3NzY5IDExLjQxNyA4LjI3NzY5IDExLjQxNyA3LjE2OTM2WiIgZmlsbD0iI0UzRTRFNSIvPgo8L3N2Zz4K'); + --str-video__icon--film-roll: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTMgNkMyLjQ1IDYgMiA2LjQ1IDIgN1YyMEMyIDIxLjEgMi45IDIyIDQgMjJIMTdDMTcuNTUgMjIgMTggMjEuNTUgMTggMjFDMTggMjAuNDUgMTcuNTUgMjAgMTcgMjBINUM0LjQ1IDIwIDQgMTkuNTUgNCAxOVY3QzQgNi40NSAzLjU1IDYgMyA2Wk0yMCAySDhDNi45IDIgNiAyLjkgNiA0VjE2QzYgMTcuMSA2LjkgMTggOCAxOEgyMEMyMS4xIDE4IDIyIDE3LjEgMjIgMTZWNEMyMiAyLjkgMjEuMSAyIDIwIDJaTTEyIDE0LjVWNS41TDE3LjQ3IDkuNkMxNy43NCA5LjggMTcuNzQgMTAuMiAxNy40NyAxMC40TDEyIDE0LjVaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); --str-video__icon--filter: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSI+CiAgICA8cGF0aCBkPSJNMSAzLjc1QS43NS43NSAwIDAgMSAxLjc1IDNoMTIuNWEuNzUuNzUgMCAwIDEgMCAxLjVIMS43NUEuNzUuNzUgMCAwIDEgMSAzLjc1em0yLjUgNEEuNzUuNzUgMCAwIDEgNC4yNSA3aDcuNWEuNzUuNzUgMCAwIDEgMCAxLjVoLTcuNWEuNzUuNzUgMCAwIDEtLjc1LS43NXpNNi43NSAxMWEuNzUuNzUgMCAwIDAgMCAxLjVoMi41YS43NS43NSAwIDAgMCAwLTEuNWgtMi41eiIgZmlsbD0iY3VycmVudENvbG9yIi8+XAo8L3N2Zz4K'); + --str-video__icon--folder: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMSAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9ImZvbGRlciI+CjxwYXRoIGlkPSJWZWN0b3IiIGQ9Ik05LjMyNjMgMy44MjQ5MkM5LjAwOTY0IDMuNTA4MjUgOC41ODQ2NCAzLjMzMzI1IDguMTQyOTcgMy4zMzMyNUgzLjgzNDY0QzIuOTE3OTcgMy4zMzMyNSAyLjE3NjMgNC4wODMyNSAyLjE3NjMgNC45OTk5MkwyLjE2Nzk3IDE0Ljk5OTlDMi4xNjc5NyAxNS45MTY2IDIuOTE3OTcgMTYuNjY2NiAzLjgzNDY0IDE2LjY2NjZIMTcuMTY4QzE4LjA4NDYgMTYuNjY2NiAxOC44MzQ2IDE1LjkxNjYgMTguODM0NiAxNC45OTk5VjYuNjY2NTlDMTguODM0NiA1Ljc0OTkyIDE4LjA4NDYgNC45OTk5MiAxNy4xNjggNC45OTk5MkgxMC41MDEzTDkuMzI2MyAzLjgyNDkyWiIgZmlsbD0iI0UzRTRFNSIvPgo8L2c+Cjwvc3ZnPgo='); --str-video__icon--fullscreen: url('data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNiAzNiIgd2lkdGg9IjM2IiBoZWlnaHQ9IjM2IiBmaWxsPSJ3aGl0ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0ibSAxMCwxNiAyLDAgMCwtNCA0LDAgMCwtMiBMIDEwLDEwIGwgMCw2IDAsMCB6Ii8+CjxwYXRoIGQ9Im0gMjAsMTAgMCwyIDQsMCAwLDQgMiwwIEwgMjYsMTAgbCAtNiwwIDAsMCB6Ii8+CjxwYXRoIGQ9Im0gMjQsMjQgLTQsMCAwLDIgTCAyNiwyNiBsIDAsLTYgLTIsMCAwLDQgMCwwIHoiLz4KPHBhdGggZD0iTSAxMiwyMCAxMCwyMCAxMCwyNiBsIDYsMCAwLC0yIC00LDAgMCwtNCAwLDAgeiIvPgo8L3N2Zz4='); - --str-video__icon--grid: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAzMyAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeD0iMzAuMzMxMSIgeT0iMTYiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAzMC4zMzExIDE2KSIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMTYuMTA5NCIgeT0iMTYiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAxNi4xMDk0IDE2KSIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMjMuMjIwNyIgeT0iMTYiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAyMy4yMjA3IDE2KSIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iOC45OTgwNSIgeT0iMTYiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA4Ljk5ODA1IDE2KSIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMzAuMzMxMSIgeT0iOS43NzczNCIgd2lkdGg9IjQuNDQ0NDQiIGhlaWdodD0iNS4zMzMzMyIgcng9IjEiIHRyYW5zZm9ybT0icm90YXRlKDkwIDMwLjMzMTEgOS43NzczNCkiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjMwLjMzMTEiIHk9IjMuNTU0NjkiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAzMC4zMzExIDMuNTU0NjkpIiBmaWxsPSJ3aGl0ZSIvPgo8cmVjdCB4PSIyMy4yMjA3IiB5PSI5Ljc3NzM0IiB3aWR0aD0iNC40NDQ0NCIgaGVpZ2h0PSI1LjMzMzMzIiByeD0iMSIgdHJhbnNmb3JtPSJyb3RhdGUoOTAgMjMuMjIwNyA5Ljc3NzM0KSIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iMjMuMjIwNyIgeT0iMy41NTQ2OSIgd2lkdGg9IjQuNDQ0NDQiIGhlaWdodD0iNS4zMzMzMyIgcng9IjEiIHRyYW5zZm9ybT0icm90YXRlKDkwIDIzLjIyMDcgMy41NTQ2OSkiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjE2LjEwOTQiIHk9IjkuNzc3MzQiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCAxNi4xMDk0IDkuNzc3MzQpIiBmaWxsPSJ3aGl0ZSIvPgo8cmVjdCB4PSIxNi4xMDk0IiB5PSIzLjU1NDY5IiB3aWR0aD0iNC40NDQ0NCIgaGVpZ2h0PSI1LjMzMzMzIiByeD0iMSIgdHJhbnNmb3JtPSJyb3RhdGUoOTAgMTYuMTA5NCAzLjU1NDY5KSIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iOC45OTgwNSIgeT0iOS43NzczNCIgd2lkdGg9IjQuNDQ0NDQiIGhlaWdodD0iNS4zMzMzMyIgcng9IjEiIHRyYW5zZm9ybT0icm90YXRlKDkwIDguOTk4MDUgOS43NzczNCkiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjguOTk4MDUiIHk9IjMuNTU0NjkiIHdpZHRoPSI0LjQ0NDQ0IiBoZWlnaHQ9IjUuMzMzMzMiIHJ4PSIxIiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA4Ljk5ODA1IDMuNTU0NjkpIiBmaWxsPSJ3aGl0ZSIvPgo8cmVjdCB4PSIxLjQ5ODA1IiB5PSIwLjUiIHdpZHRoPSIzMSIgaGVpZ2h0PSIyMyIgcng9IjIuNSIgc3Ryb2tlPSJ3aGl0ZSIvPgo8L3N2Zz4K'); + --str-video__icon--grid: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTQgMTNIMTBDMTAuNTUgMTMgMTEgMTIuNTUgMTEgMTJWNEMxMSAzLjQ1IDEwLjU1IDMgMTAgM0g0QzMuNDUgMyAzIDMuNDUgMyA0VjEyQzMgMTIuNTUgMy40NSAxMyA0IDEzWk00IDIxSDEwQzEwLjU1IDIxIDExIDIwLjU1IDExIDIwVjE2QzExIDE1LjQ1IDEwLjU1IDE1IDEwIDE1SDRDMy40NSAxNSAzIDE1LjQ1IDMgMTZWMjBDMyAyMC41NSAzLjQ1IDIxIDQgMjFaTTE0IDIxSDIwQzIwLjU1IDIxIDIxIDIwLjU1IDIxIDIwVjEyQzIxIDExLjQ1IDIwLjU1IDExIDIwIDExSDE0QzEzLjQ1IDExIDEzIDExLjQ1IDEzIDEyVjIwQzEzIDIwLjU1IDEzLjQ1IDIxIDE0IDIxWk0xMyA0VjhDMTMgOC41NSAxMy40NSA5IDE0IDlIMjBDMjAuNTUgOSAyMSA4LjU1IDIxIDhWNEMyMSAzLjQ1IDIwLjU1IDMgMjAgM0gxNEMxMy40NSAzIDEzIDMuNDUgMTMgNFoiIGZpbGw9IiNCMEI0QjciLz4KPC9nPgo8L3N2Zz4K'); --str-video__icon--info-document: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4LjUgMjNINC41QzMuNCAyMyAyLjUgMjIuMSAyLjUgMjFWN0g0LjVWMjFIMTguNVYyM1pNMTUuNSAxSDguNUM3LjQgMSA2LjUxIDEuOSA2LjUxIDNMNi41IDE3QzYuNSAxOC4xIDcuMzkgMTkgOC40OSAxOUgxOS41QzIwLjYgMTkgMjEuNSAxOC4xIDIxLjUgMTdWN0wxNS41IDFaTTE5LjUgMTdIOC41VjNIMTQuNjdMMTkuNSA3LjgzVjE3WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTE0LjgwNjIgMTQuNTY5M0gxMi44MDYyTDEyLjgwNjIgOS41NjkzNEgxNC44MDYyTDE0LjgwNjIgMTQuNTY5M1oiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik0xNC44OTAxIDYuNTczMjRDMTQuODkwMSA3LjE3MTkxIDE0LjQwNDggNy42NTcyMyAxMy44MDYyIDcuNjU3MjNDMTMuMjA3NSA3LjY1NzIzIDEyLjcyMjIgNy4xNzE5MSAxMi43MjIyIDYuNTczMjRDMTIuNzIyMiA1Ljk3NDU3IDEzLjIwNzUgNS40ODkyNiAxMy44MDYyIDUuNDg5MjZDMTQuNDA0OCA1LjQ4OTI2IDE0Ljg5MDEgNS45NzQ1NyAxNC44OTAxIDYuNTczMjRaIiBmaWxsPSJibGFjayIvPgo8L3N2Zz4K'); --str-video__icon--info-icon: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iI2ZmZmZmZiI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTExIDdoMnYyaC0yem0wIDRoMnY2aC0yem0xLTlDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4eiIvPjwvc3ZnPg=='); + --str-video__icon--layout: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNSAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IkxheW91dCI+CjxnIGlkPSJWZWN0b3IiPgo8cGF0aCBkPSJNMTEuNTI2NyAyMC42MTQzTDExLjUyNjcgMTQuNjE0M0MxMS41MjY3IDE0LjA2NDMgMTEuMDc2NyAxMy42MTQzIDEwLjUyNjcgMTMuNjE0M0w0LjUyNjY5IDEzLjYxNDNDMy45NzY2OSAxMy42MTQzIDMuNTI2NjkgMTQuMDY0MyAzLjUyNjY5IDE0LjYxNDNMMy41MjY2OSAyMC42MTQzQzMuNTI2NjkgMjEuMTY0MyAzLjk3NjY5IDIxLjYxNDMgNC41MjY2OSAyMS42MTQzTDEwLjUyNjcgMjEuNjE0M0MxMS4wNzY3IDIxLjYxNDMgMTEuNTI2NyAyMS4xNjQzIDExLjUyNjcgMjAuNjE0M1oiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTIxLjQ3MjYgMTQuNjE0M0wyMS40NzI2IDIwLjYxNDNDMjEuNDcyNiAyMS4xNjQzIDIxLjAyMjYgMjEuNjE0MyAyMC40NzI2IDIxLjYxNDNMMTQuNDcyNiAyMS42MTQzQzEzLjkyMjYgMjEuNjE0MyAxMy40NzI2IDIxLjE2NDMgMTMuNDcyNiAyMC42MTQzTDEzLjQ3MjYgMTQuNjE0M0MxMy40NzI2IDE0LjA2NDMgMTMuOTIyNiAxMy42MTQzIDE0LjQ3MjYgMTMuNjE0M0wyMC40NzI2IDEzLjYxNDNDMjEuMDIyNiAxMy42MTQzIDIxLjQ3MjYgMTQuMDY0MyAyMS40NzI2IDE0LjYxNDNaIiBmaWxsPSIjRTNFNEU1Ii8+CjxwYXRoIGQ9Ik0xMS41MjY3IDIwLjYxNDNMMTEuNTI2NyAxNC42MTQzQzExLjUyNjcgMTQuMDY0MyAxMS4wNzY3IDEzLjYxNDMgMTAuNTI2NyAxMy42MTQzTDQuNTI2NjkgMTMuNjE0M0MzLjk3NjY5IDEzLjYxNDMgMy41MjY2OSAxNC4wNjQzIDMuNTI2NjkgMTQuNjE0M0wzLjUyNjY5IDIwLjYxNDNDMy41MjY2OSAyMS4xNjQzIDMuOTc2NjkgMjEuNjE0MyA0LjUyNjY5IDIxLjYxNDNMMTAuNTI2NyAyMS42MTQzQzExLjA3NjcgMjEuNjE0MyAxMS41MjY3IDIxLjE2NDMgMTEuNTI2NyAyMC42MTQzWiIgZmlsbD0iI0UzRTRFNSIvPgo8cGF0aCBkPSJNMjEuNDcyNiAxNC42MTQzTDIxLjQ3MjYgMjAuNjE0M0MyMS40NzI2IDIxLjE2NDMgMjEuMDIyNiAyMS42MTQzIDIwLjQ3MjYgMjEuNjE0M0wxNC40NzI2IDIxLjYxNDNDMTMuOTIyNiAyMS42MTQzIDEzLjQ3MjYgMjEuMTY0MyAxMy40NzI2IDIwLjYxNDNMMTMuNDcyNiAxNC42MTQzQzEzLjQ3MjYgMTQuMDY0MyAxMy45MjI2IDEzLjYxNDMgMTQuNDcyNiAxMy42MTQzTDIwLjQ3MjYgMTMuNjE0M0MyMS4wMjI2IDEzLjYxNDMgMjEuNDcyNiAxNC4wNjQzIDIxLjQ3MjYgMTQuNjE0M1oiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTExLjUyNjcgMTAuNjU3N0wxMS41MjY3IDQuNjU3NjdDMTEuNTI2NyA0LjEwNzY3IDExLjA3NjcgMy42NTc2NyAxMC41MjY3IDMuNjU3NjdMNC41MjY2OSAzLjY1NzY3QzMuOTc2NjkgMy42NTc2NyAzLjUyNjY5IDQuMTA3NjcgMy41MjY2OSA0LjY1NzY3TDMuNTI2NjkgMTAuNjU3N0MzLjUyNjY5IDExLjIwNzcgMy45NzY2OSAxMS42NTc3IDQuNTI2NjkgMTEuNjU3N0wxMC41MjY3IDExLjY1NzdDMTEuMDc2NyAxMS42NTc3IDExLjUyNjcgMTEuMjA3NyAxMS41MjY3IDEwLjY1NzdaIiBmaWxsPSIjRTNFNEU1Ii8+CjxwYXRoIGQ9Ik0yMS40NzI3IDQuNjU3NjdMMjEuNDcyNyAxMC42NTc3QzIxLjQ3MjcgMTEuMjA3NyAyMS4wMjI3IDExLjY1NzcgMjAuNDcyNyAxMS42NTc3TDE0LjQ3MjcgMTEuNjU3N0MxMy45MjI3IDExLjY1NzcgMTMuNDcyNyAxMS4yMDc3IDEzLjQ3MjcgMTAuNjU3N0wxMy40NzI3IDQuNjU3NjdDMTMuNDcyNyA0LjEwNzY3IDEzLjkyMjcgMy42NTc2NyAxNC40NzI3IDMuNjU3NjdMMjAuNDcyNyAzLjY1NzY3QzIxLjAyMjcgMy42NTc2NyAyMS40NzI3IDQuMTA3NjcgMjEuNDcyNyA0LjY1NzY3WiIgZmlsbD0iI0UzRTRFNSIvPgo8L2c+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--layout-speaker-left: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IkxheW91dCI+CjxnIGlkPSJWZWN0b3IiPgo8cGF0aCBkPSJNMyAzLjk2OTMyTDMgMTcuMzAyN0MzIDE3Ljc2MSAzLjM3NSAxOC4xMzYgMy44MzMzMyAxOC4xMzZMMTAuNSAxOC4xMzZDMTAuOTU4MyAxOC4xMzYgMTEuMzMzMyAxNy43NjEgMTEuMzMzMyAxNy4zMDI3TDExLjMzMzMgMy45NjkzMkMxMS4zMzMzIDMuNTEwOTkgMTAuOTU4MyAzLjEzNTk5IDEwLjUgMy4xMzU5OUwzLjgzMzMzIDMuMTM1OTlDMy4zNzUgMy4xMzU5OSAzIDMuNTEwOTkgMyAzLjk2OTMyWk0xNy4xNjY3IDExLjQ2OTNMMTMuODMzMyAxMS40NjkzQzEzLjM3NSAxMS40NjkzIDEzIDExLjg0NDMgMTMgMTIuMzAyN0wxMyAxNy4zMDI3QzEzIDE3Ljc2MSAxMy4zNzUgMTguMTM2IDEzLjgzMzMgMTguMTM2TDE3LjE2NjcgMTguMTM2QzE3LjYyNSAxOC4xMzYgMTggMTcuNzYxIDE4IDE3LjMwMjdMMTggMTIuMzAyN0MxOCAxMS44NDQzIDE3LjYyNSAxMS40NjkzIDE3LjE2NjcgMTEuNDY5M1oiIGZpbGw9IiNCMEI0QjciLz4KPHBhdGggZD0iTTEzLjgzMzMgMy4xODEwMkwxNy4xNjY3IDMuMTgxMDJDMTcuNjI1IDMuMTgxMDIgMTggMy41NTYwMiAxOCA0LjAxNDM1TDE4IDkuMDE0MzVDMTggOS40NzI2OCAxNy42MjUgOS44NDc2OCAxNy4xNjY3IDkuODQ3NjhMMTMuODMzMyA5Ljg0NzY4QzEzLjM3NSA5Ljg0NzY4IDEzIDkuNDcyNjggMTMgOS4wMTQzNUwxMyA0LjAxNDM1QzEzIDMuNTU2MDIgMTMuMzc1IDMuMTgxMDIgMTMuODMzMyAzLjE4MTAyWiIgZmlsbD0iI0IwQjRCNyIvPgo8L2c+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--layout-speaker-top: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IkxheW91dCI+CjxnIGlkPSJWZWN0b3IiPgo8cGF0aCBkPSJNMTcuMTY2NyAzLjEzNTk4TDMuODMzMzMgMy4xMzU5OEMzLjM3NSAzLjEzNTk4IDMgMy41MTA5OCAzIDMuOTY5MzJMMyAxMC42MzZDMyAxMS4wOTQzIDMuMzc1IDExLjQ2OTMgMy44MzMzMyAxMS40NjkzTDE3LjE2NjcgMTEuNDY5M0MxNy42MjUgMTEuNDY5MyAxOCAxMS4wOTQzIDE4IDEwLjYzNkwxOCAzLjk2OTMyQzE4IDMuNTEwOTggMTcuNjI1IDMuMTM1OTggMTcuMTY2NyAzLjEzNTk4Wk05LjY2NjY3IDE3LjMwMjdMOS42NjY2NyAxMy45NjkzQzkuNjY2NjcgMTMuNTExIDkuMjkxNjcgMTMuMTM2IDguODMzMzMgMTMuMTM2TDMuODMzMzMgMTMuMTM2QzMuMzc1IDEzLjEzNiAzIDEzLjUxMSAzIDEzLjk2OTNMMyAxNy4zMDI3QzMgMTcuNzYxIDMuMzc1IDE4LjEzNiAzLjgzMzMzIDE4LjEzNkw4LjgzMzMzIDE4LjEzNkM5LjI5MTY3IDE4LjEzNiA5LjY2NjY3IDE3Ljc2MSA5LjY2NjY3IDE3LjMwMjdaIiBmaWxsPSIjMDA1RkZGIi8+CjxwYXRoIGQ9Ik0xNy45NTUgMTMuOTY5M0wxNy45NTUgMTcuMzAyN0MxNy45NTUgMTcuNzYxIDE3LjU4IDE4LjEzNiAxNy4xMjE2IDE4LjEzNkwxMi4xMjE2IDE4LjEzNkMxMS42NjMzIDE4LjEzNiAxMS4yODgzIDE3Ljc2MSAxMS4yODgzIDE3LjMwMjdMMTEuMjg4MyAxMy45NjkzQzExLjI4ODMgMTMuNTExIDExLjY2MzMgMTMuMTM2IDEyLjEyMTYgMTMuMTM2TDE3LjEyMTYgMTMuMTM2QzE3LjU4IDEzLjEzNiAxNy45NTUgMTMuNTExIDE3Ljk1NSAxMy45NjkzWiIgZmlsbD0iIzAwNUZGRiIvPgo8L2c+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--layout-speaker-bottom: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuMzMzMzMgMThIMTYuNjY2N0MxNy4xMjUgMTggMTcuNSAxNy42MjUgMTcuNSAxNy4xNjY3VjEwLjVDMTcuNSAxMC4wNDE3IDE3LjEyNSA5LjY2NjY3IDE2LjY2NjcgOS42NjY2N0gzLjMzMzMzQzIuODc1IDkuNjY2NjcgMi41IDEwLjA0MTcgMi41IDEwLjVWMTcuMTY2N0MyLjUgMTcuNjI1IDIuODc1IDE4IDMuMzMzMzMgMThaTTEwLjgzMzMgMy44MzMzNFY3LjE2NjY3QzEwLjgzMzMgNy42MjUgMTEuMjA4MyA4IDExLjY2NjcgOEgxNi42NjY3QzE3LjEyNSA4IDE3LjUgNy42MjUgMTcuNSA3LjE2NjY3VjMuODMzMzRDMTcuNSAzLjM3NSAxNy4xMjUgMyAxNi42NjY3IDNIMTEuNjY2N0MxMS4yMDgzIDMgMTAuODMzMyAzLjM3NSAxMC44MzMzIDMuODMzMzRaIiBmaWxsPSIjQjBCNEI3Ii8+CjxwYXRoIGQ9Ik0yLjU0NTAzIDcuMTY2NjdWMy44MzMzM0MyLjU0NTAzIDMuMzc1IDIuOTIwMDMgMyAzLjM3ODM2IDNIOC4zNzgzNkM4LjgzNjcgMyA5LjIxMTcgMy4zNzUgOS4yMTE3IDMuODMzMzNWNy4xNjY2N0M5LjIxMTcgNy42MjUgOC44MzY3IDggOC4zNzgzNiA4SDMuMzc4MzZDMi45MjAwMyA4IDIuNTQ1MDMgNy42MjUgMi41NDUwMyA3LjE2NjY3WiIgZmlsbD0iI0IwQjRCNyIvPgo8L3N2Zz4K'); + --str-video__icon--layout-speaker-right: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxIDIwTDIxIDRDMjEgMy40NSAyMC41NSAzIDIwIDNMMTIgM0MxMS40NSAzIDExIDMuNDUgMTEgNEwxMSAyMEMxMSAyMC41NSAxMS40NSAyMSAxMiAyMUwyMCAyMUMyMC41NSAyMSAyMSAyMC41NSAyMSAyMFpNNCAxMUw4IDExQzguNTUgMTEgOSAxMC41NSA5IDEwTDkgNEM5IDMuNDUgOC41NSAzIDggM0w0IDNDMy40NSAzIDMgMy40NSAzIDRMMyAxMEMzIDEwLjU1IDMuNDUgMTEgNCAxMVoiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTggMjAuOTQ2TDQgMjAuOTQ2QzMuNDUgMjAuOTQ2IDMgMjAuNDk2IDMgMTkuOTQ2TDMgMTMuOTQ2QzMgMTMuMzk2IDMuNDUgMTIuOTQ2IDQgMTIuOTQ2TDggMTIuOTQ2QzguNTUgMTIuOTQ2IDkgMTMuMzk2IDkgMTMuOTQ2TDkgMTkuOTQ2QzkgMjAuNDk2IDguNTUgMjAuOTQ2IDggMjAuOTQ2WiIgZmlsbD0iI0UzRTRFNSIvPgo8L3N2Zz4K'); + --str-video__icon--layout-speaker-one-on-one: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNCAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yLjk1NzAzIDIxLjA1ODNWMy4zOTY0OEMyLjk1NzAzIDIuODQ2NDggMy40MDcwMyAyLjM5NjQ4IDMuOTU3MDMgMi4zOTY0OEgyMC4wNDM2QzIwLjU5MzYgMi4zOTY0OCAyMS4wNDM2IDIuODQ2NDggMjEuMDQzNiAzLjM5NjQ4TDIxLjA0MzYgMjEuMDU4M0MyMS4wNDM2IDIxLjYwODMgMjAuNTkzNiAyMi4wNTgzIDIwLjA0MzYgMjIuMDU4M0gzLjk1NzAzQzMuNDA3MDMgMjIuMDU4MyAyLjk1NzAzIDIxLjYwODMgMi45NTcwMyAyMS4wNTgzWk0xOC4zMDA1IDQuMjI3NTRMMTQuMzAwNSA0LjIyNzU0QzEzLjc1MDUgNC4yMjc1NCAxMy4zMDA1IDQuNjc3NTQgMTMuMzAwNSA1LjIyNzU0VjExLjIyNzVDMTMuMzAwNSAxMS43Nzc1IDEzLjc1MDUgMTIuMjI3NSAxNC4zMDA1IDEyLjIyNzVIMTguMzAwNUMxOC44NTA1IDEyLjIyNzUgMTkuMzAwNSAxMS43Nzc1IDE5LjMwMDUgMTEuMjI3NVY1LjIyNzU0QzE5LjMwMDUgNC42Nzc1NCAxOC44NTA1IDQuMjI3NTQgMTguMzAwNSA0LjIyNzU0WiIgZmlsbD0iI0UzRTRFNSIvPgo8L3N2Zz4K'); + --str-video__icon--layout-speaker-live-stream: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNCAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuNzYgMTcuMDc1QzYuNjcgMTUuOTk1IDYgMTQuNDk1IDYgMTIuODM1QzYgMTEuMTc1IDYuNjcgOS42NzQ5NSA3Ljc2IDguNTk0OTVMOS4xOCAxMC4wMTVDOC40NSAxMC43MzUgOCAxMS43MzUgOCAxMi44MzVDOCAxMy45MzUgOC40NSAxNC45MzUgOS4xNyAxNS42NjVMNy43NiAxNy4wNzVaTTE2LjI0IDE3LjA3NUMxNy4zMyAxNS45OTUgMTggMTQuNDk1IDE4IDEyLjgzNUMxOCAxMS4xNzUgMTcuMzMgOS42NzQ5NSAxNi4yNCA4LjU5NDk1TDE0LjgyIDEwLjAxNUMxNS41NSAxMC43MzUgMTYgMTEuNzM1IDE2IDEyLjgzNUMxNiAxMy45MzUgMTUuNTUgMTQuOTM1IDE0LjgzIDE1LjY2NUwxNi4yNCAxNy4wNzVaTTEyIDEwLjgzNUMxMC45IDEwLjgzNSAxMCAxMS43MzUgMTAgMTIuODM1QzEwIDEzLjkzNSAxMC45IDE0LjgzNSAxMiAxNC44MzVDMTMuMSAxNC44MzUgMTQgMTMuOTM1IDE0IDEyLjgzNUMxNCAxMS43MzUgMTMuMSAxMC44MzUgMTIgMTAuODM1Wk0yMCAxMi44MzVDMjAgMTUuMDQ1IDE5LjEgMTcuMDQ1IDE3LjY1IDE4LjQ4NUwxOS4wNyAxOS45MDVDMjAuODggMTguMDk1IDIyIDE1LjU5NSAyMiAxMi44MzVDMjIgMTAuMDc1IDIwLjg4IDcuNTc0OTUgMTkuMDcgNS43NjQ5NUwxNy42NSA3LjE4NDk1QzE5LjEgOC42MjQ5NSAyMCAxMC42MjUgMjAgMTIuODM1Wk02LjM1IDcuMTg0OTVMNC45MyA1Ljc2NDk1QzMuMTIgNy41NzQ5NSAyIDEwLjA3NSAyIDEyLjgzNUMyIDE1LjU5NSAzLjEyIDE4LjA5NSA0LjkzIDE5LjkwNUw2LjM1IDE4LjQ4NUM0LjkgMTcuMDQ1IDQgMTUuMDQ1IDQgMTIuODM1QzQgMTAuNjI1IDQuOSA4LjYyNDk1IDYuMzUgNy4xODQ5NVoiIGZpbGw9IiNFM0U0RTUiLz4KPC9zdmc+Cg=='); + --str-video__icon--language: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTExLjk5IDJDNi40NyAyIDIgNi40OCAyIDEyQzIgMTcuNTIgNi40NyAyMiAxMS45OSAyMkMxNy41MiAyMiAyMiAxNy41MiAyMiAxMkMyMiA2LjQ4IDE3LjUyIDIgMTEuOTkgMlpNMTguOTIgOEgxNS45N0MxNS42NSA2Ljc1IDE1LjE5IDUuNTUgMTQuNTkgNC40NEMxNi40MyA1LjA3IDE3Ljk2IDYuMzUgMTguOTIgOFpNMTIgNC4wNEMxMi44MyA1LjI0IDEzLjQ4IDYuNTcgMTMuOTEgOEgxMC4wOUMxMC41MiA2LjU3IDExLjE3IDUuMjQgMTIgNC4wNFpNNC4yNiAxNEM0LjEgMTMuMzYgNCAxMi42OSA0IDEyQzQgMTEuMzEgNC4xIDEwLjY0IDQuMjYgMTBINy42NEM3LjU2IDEwLjY2IDcuNSAxMS4zMiA3LjUgMTJDNy41IDEyLjY4IDcuNTYgMTMuMzQgNy42NCAxNEg0LjI2Wk01LjA4IDE2SDguMDNDOC4zNSAxNy4yNSA4LjgxIDE4LjQ1IDkuNDEgMTkuNTZDNy41NyAxOC45MyA2LjA0IDE3LjY2IDUuMDggMTZaTTguMDMgOEg1LjA4QzYuMDQgNi4zNCA3LjU3IDUuMDcgOS40MSA0LjQ0QzguODEgNS41NSA4LjM1IDYuNzUgOC4wMyA4Wk0xMiAxOS45NkMxMS4xNyAxOC43NiAxMC41MiAxNy40MyAxMC4wOSAxNkgxMy45MUMxMy40OCAxNy40MyAxMi44MyAxOC43NiAxMiAxOS45NlpNMTQuMzQgMTRIOS42NkM5LjU3IDEzLjM0IDkuNSAxMi42OCA5LjUgMTJDOS41IDExLjMyIDkuNTcgMTAuNjUgOS42NiAxMEgxNC4zNEMxNC40MyAxMC42NSAxNC41IDExLjMyIDE0LjUgMTJDMTQuNSAxMi42OCAxNC40MyAxMy4zNCAxNC4zNCAxNFpNMTQuNTkgMTkuNTZDMTUuMTkgMTguNDUgMTUuNjUgMTcuMjUgMTUuOTcgMTZIMTguOTJDMTcuOTYgMTcuNjUgMTYuNDMgMTguOTMgMTQuNTkgMTkuNTZaTTE2LjM2IDE0QzE2LjQ0IDEzLjM0IDE2LjUgMTIuNjggMTYuNSAxMkMxNi41IDExLjMyIDE2LjQ0IDEwLjY2IDE2LjM2IDEwSDE5Ljc0QzE5LjkgMTAuNjQgMjAgMTEuMzEgMjAgMTJDMjAgMTIuNjkgMTkuOSAxMy4zNiAxOS43NCAxNEgxNi4zNloiIGZpbGw9IiNCMEI0QjciLz4KPC9nPgo8L3N2Zz4K'); --str-video__icon--livestream-viewers: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzg2OTdfMjU1NykiPgo8cGF0aCBkPSJNOC4zNzc0NCA0LjU2MjVDMTAuNzQ2MiA0LjU2MjUgMTIuODU4NyA1Ljg5Mzc1IDEzLjg4OTkgOEMxMi44NTg3IDEwLjEwNjIgMTAuNzQ2MiAxMS40Mzc1IDguMzc3NDQgMTEuNDM3NUM2LjAwODY5IDExLjQzNzUgMy44OTYxOSAxMC4xMDYyIDIuODY0OTQgOEMzLjg5NjE5IDUuODkzNzUgNi4wMDg2OSA0LjU2MjUgOC4zNzc0NCA0LjU2MjVaTTguMzc3NDQgMy4zMTI1QzUuMjUyNDQgMy4zMTI1IDIuNTgzNjkgNS4yNTYyNSAxLjUwMjQ0IDhDMi41ODM2OSAxMC43NDM4IDUuMjUyNDQgMTIuNjg3NSA4LjM3NzQ0IDEyLjY4NzVDMTEuNTAyNCAxMi42ODc1IDE0LjE3MTIgMTAuNzQzOCAxNS4yNTI0IDhDMTQuMTcxMiA1LjI1NjI1IDExLjUwMjQgMy4zMTI1IDguMzc3NDQgMy4zMTI1Wk04LjM3NzQ0IDYuNDM3NUM5LjIzOTk0IDYuNDM3NSA5LjkzOTk0IDcuMTM3NSA5LjkzOTk0IDhDOS45Mzk5NCA4Ljg2MjUgOS4yMzk5NCA5LjU2MjUgOC4zNzc0NCA5LjU2MjVDNy41MTQ5NCA5LjU2MjUgNi44MTQ5NCA4Ljg2MjUgNi44MTQ5NCA4QzYuODE0OTQgNy4xMzc1IDcuNTE0OTQgNi40Mzc1IDguMzc3NDQgNi40Mzc1Wk04LjM3NzQ0IDUuMTg3NUM2LjgyNzQ0IDUuMTg3NSA1LjU2NDk0IDYuNDUgNS41NjQ5NCA4QzUuNTY0OTQgOS41NSA2LjgyNzQ0IDEwLjgxMjUgOC4zNzc0NCAxMC44MTI1QzkuOTI3NDQgMTAuODEyNSAxMS4xODk5IDkuNTUgMTEuMTg5OSA4QzExLjE4OTkgNi40NSA5LjkyNzQ0IDUuMTg3NSA4LjM3NzQ0IDUuMTg3NVoiIGZpbGw9IiNGQ0ZDRkMiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF84Njk3XzI1NTciPgo8cmVjdCB4PSIwLjg3NzQ0MSIgeT0iMC41IiB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIHJ4PSI0IiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo='); --str-video__icon--loading: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzEiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMSAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMzAuOTIxMyAxNUMzMC45MjEzIDIzLjI4NDMgMjQuMjA1NiAzMCAxNS45MjEzIDMwQzEwLjcxNTQgMzAgNi4xMjg5OSAyNy4zNDggMy40MzkyMSAyMy4zMjE0TDUuMTAzNDkgMjIuMjExOUM3LjQzNDYzIDI1LjcwMTYgMTEuNDA5NiAyOCAxNS45MjEzIDI4QzIzLjEwMSAyOCAyOC45MjEzIDIyLjE3OTcgMjguOTIxMyAxNUMyOC45MjEzIDcuODIwMyAyMy4xMDEgMiAxNS45MjEzIDJMMTUuOTIxMyAwQzI0LjIwNTYgMCAzMC45MjEzIDYuNzE1NzMgMzAuOTIxMyAxNVoiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl8xODYwXzExMDYyMykiLz4KICAgIDxkZWZzPgogICAgICAgIDxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl8xODYwXzExMDYyMyIgeDE9IjMuNDM5MjEiIHkxPSIwIiB4Mj0iMy40MzkyMSIgeTI9IjMwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgICAgICAgIDxzdG9wIGlkPSJzdG9wLXNlbWl0cmFuc3BhcmVudCIgc3RvcC1vcGFjaXR5PSIwLjAxIi8+CiAgICAgICAgICAgIDxzdG9wIGlkPSJzdG9wLW9wYXF1ZSIgb2Zmc2V0PSIxIiAvPgogICAgICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICA8L2RlZnM+Cjwvc3ZnPg=='); + --str-video__icon--logout: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNSAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMuMDA2NTEgMi45MTY2N0g2LjUwNjUxQzYuODI3MzQgMi45MTY2NyA3LjA4OTg0IDIuNjU0MTcgNy4wODk4NCAyLjMzMzMzQzcuMDg5ODQgMi4wMTI1IDYuODI3MzQgMS43NSA2LjUwNjUxIDEuNzVIMy4wMDY1MUMyLjM2NDg0IDEuNzUgMS44Mzk4NCAyLjI3NSAxLjgzOTg0IDIuOTE2NjdWMTEuMDgzM0MxLjgzOTg0IDExLjcyNSAyLjM2NDg0IDEyLjI1IDMuMDA2NTEgMTIuMjVINi41MDY1MUM2LjgyNzM0IDEyLjI1IDcuMDg5ODQgMTEuOTg3NSA3LjA4OTg0IDExLjY2NjdDNy4wODk4NCAxMS4zNDU4IDYuODI3MzQgMTEuMDgzMyA2LjUwNjUxIDExLjA4MzNIMy4wMDY1MVYyLjkxNjY3WiIgZmlsbD0iI0UzRTRFNSIvPgo8cGF0aCBkPSJNMTIuMTM1NyA2Ljc5NTgzTDEwLjUwODIgNS4xNjgzM0MxMC4zMjE1IDQuOTgxNjcgMTAuMDA2NSA1LjExIDEwLjAwNjUgNS4zNzI1VjYuNDE2NjdINS45MjMxOEM1LjYwMjM0IDYuNDE2NjcgNS4zMzk4NCA2LjY3OTE3IDUuMzM5ODQgN0M1LjMzOTg0IDcuMzIwODMgNS42MDIzNCA3LjU4MzMzIDUuOTIzMTggNy41ODMzM0gxMC4wMDY1VjguNjI3NUMxMC4wMDY1IDguODkgMTAuMzIxNSA5LjAxODMzIDEwLjUwMjMgOC44MzE2N0wxMi4xMjk4IDcuMjA0MTdDMTIuMjQ2NSA3LjA5MzMzIDEyLjI0NjUgNi45MDY2NyAxMi4xMzU3IDYuNzk1ODNaIiBmaWxsPSIjRTNFNEU1Ii8+Cjwvc3ZnPgo='); + --str-video__icon--login: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNCAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwLjMgNy44MDA1OUM5LjkxIDguMTkwNTkgOS45MSA4LjgxMDU5IDEwLjMgOS4yMDA1OUwxMi4yIDExLjEwMDZIM0MyLjQ1IDExLjEwMDYgMiAxMS41NTA2IDIgMTIuMTAwNkMyIDEyLjY1MDYgMi40NSAxMy4xMDA2IDMgMTMuMTAwNkgxMi4yTDEwLjMgMTUuMDAwNkM5LjkxIDE1LjM5MDYgOS45MSAxNi4wMTA2IDEwLjMgMTYuNDAwNkMxMC42OSAxNi43OTA2IDExLjMxIDE2Ljc5MDYgMTEuNyAxNi40MDA2TDE1LjI5IDEyLjgxMDZDMTUuNjggMTIuNDIwNiAxNS42OCAxMS43OTA2IDE1LjI5IDExLjQwMDZMMTEuNyA3LjgwMDU5QzExLjMxIDcuNDEwNTkgMTAuNjkgNy40MTA1OSAxMC4zIDcuODAwNTlaTTIwIDE5LjEwMDZIMTNDMTIuNDUgMTkuMTAwNiAxMiAxOS41NTA2IDEyIDIwLjEwMDZDMTIgMjAuNjUwNiAxMi40NSAyMS4xMDA2IDEzIDIxLjEwMDZIMjBDMjEuMSAyMS4xMDA2IDIyIDIwLjIwMDYgMjIgMTkuMTAwNlY1LjEwMDU5QzIyIDQuMDAwNTkgMjEuMSAzLjEwMDU5IDIwIDMuMTAwNTlIMTNDMTIuNDUgMy4xMDA1OSAxMiAzLjU1MDU5IDEyIDQuMTAwNTlDMTIgNC42NTA1OSAxMi40NSA1LjEwMDU5IDEzIDUuMTAwNTlIMjBWMTkuMTAwNloiIGZpbGw9IiNFM0U0RTUiLz4KPC9zdmc+Cg=='); --str-video__icon--magnifier-glass: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik04LjQ4NDkxIDcuODYwM0g3Ljk1NTk3TDcuNzY4NSA3LjY3OTUyQzguNTcxOTUgNi43NDIxNiA4Ljk4NzA2IDUuNDYzMzQgOC43NTk0MiA0LjEwNDE4QzguNDQ0NzMgMi4yNDI4NSA2Ljg5MTQgMC43NTY0NzQgNS4wMTY2OSAwLjUyODgzQzIuMTg0NTMgMC4xODA2NjkgLTAuMTk5MDMxIDIuNTY0MjMgMC4xNDkxMyA1LjM5NjM5QzAuMzc2Nzc0IDcuMjcxMSAxLjg2MzE1IDguODI0NDMgMy43MjQ0OCA5LjEzOTEyQzUuMDgzNjQgOS4zNjY3NiA2LjM2MjQ2IDguOTUxNjUgNy4yOTk4MiA4LjE0ODJMNy40ODA2IDguMzM1NjdWOC44NjQ2MUwxMC4zMjYxIDExLjcxMDJDMTAuNjAwNyAxMS45ODQ3IDExLjA0OTIgMTEuOTg0NyAxMS4zMjM4IDExLjcxMDJDMTEuNTk4MyAxMS40MzU2IDExLjU5ODMgMTAuOTg3IDExLjMyMzggMTAuNzEyNUw4LjQ4NDkxIDcuODYwM1pNNC40Njc2NiA3Ljg2MDNDMi44MDA1MSA3Ljg2MDMgMS40NTQ3MyA2LjUxNDUyIDEuNDU0NzMgNC44NDczNkMxLjQ1NDczIDMuMTgwMjEgMi44MDA1MSAxLjgzNDQzIDQuNDY3NjYgMS44MzQ0M0M2LjEzNDgyIDEuODM0NDMgNy40ODA2IDMuMTgwMjEgNy40ODA2IDQuODQ3MzZDNy40ODA2IDYuNTE0NTIgNi4xMzQ4MiA3Ljg2MDMgNC40Njc2NiA3Ljg2MDNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); - --str-video__icon--mic: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDE0Ljg5NDdDMTMuODk3MSAxNC44OTQ3IDE1LjQyODYgMTMuMzQzMiAxNS40Mjg2IDExLjQyMTFWNC40NzM2OEMxNS40Mjg2IDIuNTUxNTggMTMuODk3MSAxIDEyIDFDMTAuMTAyOSAxIDguNTcxNDMgMi41NTE1OCA4LjU3MTQzIDQuNDczNjhWMTEuNDIxMUM4LjU3MTQzIDEzLjM0MzIgMTAuMTAyOSAxNC44OTQ3IDEyIDE0Ljg5NDdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTcuNzE0MyAxMS40MjExQzE3LjcxNDMgMTQuNjE2OCAxNS4xNTQzIDE3LjIxMDUgMTIgMTcuMjEwNUM4Ljg0NTcxIDE3LjIxMDUgNi4yODU3MSAxNC42MTY4IDYuMjg1NzEgMTEuNDIxMUg0QzQgMTUuNTA4NCA2Ljk4Mjg2IDE4Ljg2NjMgMTAuODU3MSAxOS40MzM3VjIzSDEzLjE0MjlWMTkuNDMzN0MxNy4wMTcxIDE4Ljg2NjMgMjAgMTUuNTA4NCAyMCAxMS40MjExSDE3LjcxNDNaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'); - --str-video__icon--mic-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5Ljc3NzggMTEuNDIxMUgxNy44ODg5QzE3Ljg4ODkgMTIuMjc3OSAxNy43MTExIDEzLjA3NjggMTcuNDExMSAxMy43OTQ3TDE4Ljc3NzggMTUuMjE4OUMxOS40IDE0LjA4NDIgMTkuNzc3OCAxMi43OTg5IDE5Ljc3NzggMTEuNDIxMVpNMTUuMzExMSAxMS42MTc5QzE1LjMxMTEgMTEuNTQ4NCAxNS4zMzMzIDExLjQ5MDUgMTUuMzMzMyAxMS40MjExVjQuNDczNjhDMTUuMzMzMyAyLjU1MTU4IDEzLjg0NDQgMSAxMiAxQzEwLjE1NTYgMSA4LjY2NjY3IDIuNTUxNTggOC42NjY2NyA0LjQ3MzY4VjQuNjgyMTFMMTUuMzExMSAxMS42MTc5Wk0zLjQxMTExIDIuMTU3ODlMMiAzLjYyODQyTDguNjc3NzggMTAuNTg3NFYxMS40MjExQzguNjc3NzggMTMuMzQzMiAxMC4xNTU2IDE0Ljg5NDcgMTIgMTQuODk0N0MxMi4yNDQ0IDE0Ljg5NDcgMTIuNDg4OSAxNC44NiAxMi43MjIyIDE0LjgwMjFMMTQuNTY2NyAxNi43MjQyQzEzLjc3NzggMTcuMTA2MyAxMi45IDE3LjMyNjMgMTIgMTcuMzI2M0M4LjkzMzMzIDE3LjMyNjMgNi4xMTExMSAxNC44OTQ3IDYuMTExMTEgMTEuNDIxMUg0LjIyMjIyQzQuMjIyMjIgMTUuMzY5NSA3LjI0NDQ0IDE4LjYzNDcgMTAuODg4OSAxOS4yMDIxVjIzSDEzLjExMTFWMTkuMjAyMUMxNC4xMjIyIDE5LjA1MTYgMTUuMDc3OCAxOC42ODExIDE1LjkzMzMgMTguMTZMMjAuNTg4OSAyM0wyMiAyMS41Mjk1TDMuNDExMTEgMi4xNTc4OVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo='); - --str-video__icon--network-quality: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNCAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3QgeT0iMC41IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSI2IiBmaWxsPSJibGFjayIgZmlsbC1vcGFjaXR5PSIwLjg1Ii8+CjxwYXRoIGQ9Ik0xMiAxNi41TDEyIDExLjUiIHN0cm9rZT0iIzAwNkNGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTcgMTYuNUw3IDE0LjUiIHN0cm9rZT0iIzAwNkNGRiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTE3IDE2LjVMMTcgOC41IiBzdHJva2U9IiMwMDZDRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPg=='); - --str-video__icon--participants: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzEiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMSAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0yMS4zMzc1IDE2LjM5OTZDMjMuMDUgMTcuNTI1OCAyNC4yNSAxOS4wNTE2IDI0LjI1IDIxLjA4NTlWMjQuNzE4OEgyOS4yNVYyMS4wODU5QzI5LjI1IDE4LjQ0NjEgMjQuNzg3NSAxNi44ODQgMjEuMzM3NSAxNi4zOTk2WiIgZmlsbD0id2hpdGUiLz4KICAgIDxwYXRoIGQ9Ik0xOS4yNSAxNS4wMzEyQzIyLjAxMjUgMTUuMDMxMiAyNC4yNSAxMi44NjM3IDI0LjI1IDEwLjE4NzVDMjQuMjUgNy41MTEzMyAyMi4wMTI1IDUuMzQzNzUgMTkuMjUgNS4zNDM3NUMxOC42NjI1IDUuMzQzNzUgMTguMTEyNSA1LjQ2NDg0IDE3LjU4NzUgNS42MzQzN0MxOC42MjUgNi44ODE2NCAxOS4yNSA4LjQ2Nzk3IDE5LjI1IDEwLjE4NzVDMTkuMjUgMTEuOTA3IDE4LjYyNSAxMy40OTM0IDE3LjU4NzUgMTQuNzQwNkMxOC4xMTI1IDE0LjkxMDIgMTguNjYyNSAxNS4wMzEyIDE5LjI1IDE1LjAzMTJaIiBmaWxsPSJ3aGl0ZSIvPgogICAgPHBhdGggZD0iTTExLjc1IDE1LjAzMTJDMTQuNTEyNSAxNS4wMzEyIDE2Ljc1IDEyLjg2MzcgMTYuNzUgMTAuMTg3NUMxNi43NSA3LjUxMTMzIDE0LjUxMjUgNS4zNDM3NSAxMS43NSA1LjM0Mzc1QzguOTg3NSA1LjM0Mzc1IDYuNzUgNy41MTEzMyA2Ljc1IDEwLjE4NzVDNi43NSAxMi44NjM3IDguOTg3NSAxNS4wMzEyIDExLjc1IDE1LjAzMTJaTTExLjc1IDcuNzY1NjJDMTMuMTI1IDcuNzY1NjIgMTQuMjUgOC44NTU0NyAxNC4yNSAxMC4xODc1QzE0LjI1IDExLjUxOTUgMTMuMTI1IDEyLjYwOTQgMTEuNzUgMTIuNjA5NEMxMC4zNzUgMTIuNjA5NCA5LjI1IDExLjUxOTUgOS4yNSAxMC4xODc1QzkuMjUgOC44NTU0NyAxMC4zNzUgNy43NjU2MiAxMS43NSA3Ljc2NTYyWiIgZmlsbD0id2hpdGUiLz4KICAgIDxwYXRoIGQ9Ik0xMS43NSAxNi4yNDIyQzguNDEyNSAxNi4yNDIyIDEuNzUgMTcuODY0OCAxLjc1IDIxLjA4NTlWMjQuNzE4OEgyMS43NVYyMS4wODU5QzIxLjc1IDE3Ljg2NDggMTUuMDg3NSAxNi4yNDIyIDExLjc1IDE2LjI0MjJaTTE5LjI1IDIyLjI5NjlINC4yNVYyMS4wOThDNC41IDIwLjIyNjIgOC4zNzUgMTguNjY0MSAxMS43NSAxOC42NjQxQzE1LjEyNSAxOC42NjQxIDE5IDIwLjIyNjIgMTkuMjUgMjEuMDg1OVYyMi4yOTY5WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=='); - --str-video__icon--reactions: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNiAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE3LjM3NSAxMS4zMjAzQzE4LjQxMDUgMTEuMzIwMyAxOS4yNSAxMC41MDcxIDE5LjI1IDkuNTAzOTFDMTkuMjUgOC41MDA3MyAxOC40MTA1IDcuNjg3NSAxNy4zNzUgNy42ODc1QzE2LjMzOTUgNy42ODc1IDE1LjUgOC41MDA3MyAxNS41IDkuNTAzOTFDMTUuNSAxMC41MDcxIDE2LjMzOTUgMTEuMzIwMyAxNy4zNzUgMTEuMzIwM1oiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik04LjYyNSAxMS4zMjAzQzkuNjYwNTMgMTEuMzIwMyAxMC41IDEwLjUwNzEgMTAuNSA5LjUwMzkxQzEwLjUgOC41MDA3MyA5LjY2MDUzIDcuNjg3NSA4LjYyNSA3LjY4NzVDNy41ODk0NyA3LjY4NzUgNi43NSA4LjUwMDczIDYuNzUgOS41MDM5MUM2Ljc1IDEwLjUwNzEgNy41ODk0NyAxMS4zMjAzIDguNjI1IDExLjMyMDNaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTMgMTkuNzk2OUMxNS44NSAxOS43OTY5IDE4LjI3NSAxNy43ODY3IDE5LjI1IDE0Ljk1MzFINi43NUM3LjcyNSAxNy43ODY3IDEwLjE1IDE5Ljc5NjkgMTMgMTkuNzk2OVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMi45ODc1IDAuNDIxODc1QzYuMDg3NSAwLjQyMTg3NSAwLjUgNS44NDY4OCAwLjUgMTIuNTMxMkMwLjUgMTkuMjE1NiA2LjA4NzUgMjQuNjQwNiAxMi45ODc1IDI0LjY0MDZDMTkuOSAyNC42NDA2IDI1LjUgMTkuMjE1NiAyNS41IDEyLjUzMTJDMjUuNSA1Ljg0Njg4IDE5LjkgMC40MjE4NzUgMTIuOTg3NSAwLjQyMTg3NVpNMTMgMjIuMjE4OEM3LjQ3NSAyMi4yMTg4IDMgMTcuODgzNiAzIDEyLjUzMTJDMyA3LjE3ODkxIDcuNDc1IDIuODQzNzUgMTMgMi44NDM3NUMxOC41MjUgMi44NDM3NSAyMyA3LjE3ODkxIDIzIDEyLjUzMTJDMjMgMTcuODgzNiAxOC41MjUgMjIuMjE4OCAxMyAyMi4yMTg4WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=='); - --str-video__icon--recording-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNiAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzIDAuNDIxODc1QzYuMSAwLjQyMTg3NSAwLjUgNS44NDY4OCAwLjUgMTIuNTMxMkMwLjUgMTkuMjE1NiA2LjEgMjQuNjQwNiAxMyAyNC42NDA2QzE5LjkgMjQuNjQwNiAyNS41IDE5LjIxNTYgMjUuNSAxMi41MzEyQzI1LjUgNS44NDY4OCAxOS45IDAuNDIxODc1IDEzIDAuNDIxODc1Wk0xMyAyMi4yMTg4QzcuNDc1IDIyLjIxODggMyAxNy44ODM2IDMgMTIuNTMxMkMzIDcuMTc4OTEgNy40NzUgMi44NDM3NSAxMyAyLjg0Mzc1QzE4LjUyNSAyLjg0Mzc1IDIzIDcuMTc4OTEgMjMgMTIuNTMxMkMyMyAxNy44ODM2IDE4LjUyNSAyMi4yMTg4IDEzIDIyLjIxODhaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTMgMTguNTg1OUMxNi40NTE4IDE4LjU4NTkgMTkuMjUgMTUuODc1MiAxOS4yNSAxMi41MzEyQzE5LjI1IDkuMTg3MzQgMTYuNDUxOCA2LjQ3NjU2IDEzIDYuNDc2NTZDOS41NDgyMiA2LjQ3NjU2IDYuNzUgOS4xODczNCA2Ljc1IDEyLjUzMTJDNi43NSAxNS44NzUyIDkuNTQ4MjIgMTguNTg1OSAxMyAxOC41ODU5WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=='); - --str-video__icon--recording-on: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNiAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEzIDAuNDIxODc1QzYuMSAwLjQyMTg3NSAwLjUgNS44NDY4OCAwLjUgMTIuNTMxMkMwLjUgMTkuMjE1NiA2LjEgMjQuNjQwNiAxMyAyNC42NDA2QzE5LjkgMjQuNjQwNiAyNS41IDE5LjIxNTYgMjUuNSAxMi41MzEyQzI1LjUgNS44NDY4OCAxOS45IDAuNDIxODc1IDEzIDAuNDIxODc1Wk0xMyAyMi4yMTg4QzcuNDc1IDIyLjIxODggMyAxNy44ODM2IDMgMTIuNTMxMkMzIDcuMTc4OTEgNy40NzUgMi44NDM3NSAxMyAyLjg0Mzc1QzE4LjUyNSAyLjg0Mzc1IDIzIDcuMTc4OTEgMjMgMTIuNTMxMkMyMyAxNy44ODM2IDE4LjUyNSAyMi4yMTg4IDEzIDIyLjIxODhaIiBmaWxsPSIjZDkzMDI1Ii8+CjxwYXRoIGQ9Ik0xMyAxOC41ODU5QzE2LjQ1MTggMTguNTg1OSAxOS4yNSAxNS44NzUyIDE5LjI1IDEyLjUzMTJDMTkuMjUgOS4xODczNCAxNi40NTE4IDYuNDc2NTYgMTMgNi40NzY1NkM5LjU0ODIyIDYuNDc2NTYgNi43NSA5LjE4NzM0IDYuNzUgMTIuNTMxMkM2Ljc1IDE1Ljg3NTIgOS41NDgyMiAxOC41ODU5IDEzIDE4LjU4NTlaIiBmaWxsPSIjZDkzMDI1Ii8+Cjwvc3ZnPgo='); + --str-video__icon--mediation: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9Im1lZGlhdGlvbiI+CjxwYXRoIGlkPSJWZWN0b3IiIGQ9Ik0xNS4wOTEyIDEwLjgzNDJIMTAuODc0NUMxMC41OTEyIDEzLjQxNzYgOC45OTEyIDE1LjYwMDkgNi43NTc4NiAxNi43MDkyQzYuNzMyODYgMTguMjE3NiA1LjM3NDUzIDE5LjQwMDkgMy43OTk1MyAxOS4xMjU5QzIuNzk5NTMgMTguOTUwOSAxLjk3NDUzIDE4LjEyNTkgMS43OTk1MyAxNy4xMjU5QzEuNTE2MiAxNS41NDI2IDIuNzI0NTMgMTQuMTY3NiA0LjI1Nzg2IDE0LjE2NzZDNS4wNDk1MyAxNC4xNjc2IDUuNzQxMiAxNC41NDI2IDYuMTk5NTMgMTUuMTE3NkM3Ljc4Mjg2IDE0LjI1OTIgOC45MTYyIDEyLjY5MjYgOS4xODI4NiAxMC44MzQySDYuNTk5NTNDNi4xOTk1MyAxMS45NTA5IDUuMDQ5NTMgMTIuNzAwOSAzLjc0OTUzIDEyLjQ1MDlDMi43NjYyIDEyLjI1OTIgMS45NzQ1MyAxMS40NTA5IDEuNzkxMiAxMC40Njc2QzEuNTA3ODYgOC44ODQyMyAyLjcyNDUzIDcuNTAwOSA0LjI1Nzg2IDcuNTAwOUM1LjM0MTIgNy41MDA5IDYuMjU3ODYgOC4yMDA5IDYuNjA3ODYgOS4xNjc1Nkg5LjE5MTJDOC45MjQ1MyA3LjMwOTIzIDcuNzgyODYgNS43NTA5IDYuMTk5NTMgNC44ODQyM0M1LjY2NjIgNS41NTA5IDQuODA3ODYgNS45NTA5IDMuODU3ODYgNS44MDA5QzIuODMyODYgNS42NDI1NiAxLjk3NDUzIDQuODA5MjMgMS43OTk1MyAzLjc4NDIzQzEuNTI0NTMgMi4yMDkyMyAyLjczMjg2IDAuODM0MjI5IDQuMjU3ODYgMC44MzQyMjlDNS42MjQ1MyAwLjgzNDIyOSA2LjcyNDUzIDEuOTI1OSA2Ljc0OTUzIDMuMjkyNTZDOC45ODI4NiA0LjQwMDkgMTAuNTgyOSA2LjU4NDIzIDEwLjg2NjIgOS4xNjc1NkgxNS4wOTEyVjcuNjc1OUMxNS4wOTEyIDcuMzAwOSAxNS41NDEyIDcuMTE3NTYgMTUuNzk5NSA3LjM4NDIzTDE4LjEyNDUgOS43MDkyM0MxOC4yOTEyIDkuODc1ODkgMTguMjkxMiAxMC4xMzQyIDE4LjEyNDUgMTAuMzAwOUwxNS43OTk1IDEyLjYyNTlDMTUuNTQxMiAxMi44ODQyIDE1LjA5MTIgMTIuNzAwOSAxNS4wOTEyIDEyLjMyNTlWMTAuODM0MloiIGZpbGw9IiNFM0U0RTUiLz4KPC9nPgo8L3N2Zz4K'); + --str-video__icon--mic: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTExLjk5OSAxNC41QzEzLjY1OSAxNC41IDE0Ljk5OSAxMy4xNiAxNC45OTkgMTEuNVY1LjVDMTQuOTk5IDMuODQgMTMuNjU5IDIuNSAxMS45OTkgMi41QzEwLjMzOSAyLjUgOC45OTkwMSAzLjg0IDguOTk5MDEgNS41VjExLjVDOC45OTkwMSAxMy4xNiAxMC4zMzkgMTQuNSAxMS45OTkgMTQuNVpNMTcuOTA5IDExLjVDMTcuNDE5IDExLjUgMTcuMDA5IDExLjg2IDE2LjkyOSAxMi4zNUMxNi41MTkgMTQuNyAxNC40NjkgMTYuNSAxMS45OTkgMTYuNUM5LjUyOTAxIDE2LjUgNy40NzkwMSAxNC43IDcuMDY5MDEgMTIuMzVDNi45ODkwMSAxMS44NiA2LjU3OTAxIDExLjUgNi4wODkwMSAxMS41QzUuNDc5MDEgMTEuNSA0Ljk5OTAxIDEyLjA0IDUuMDg5MDEgMTIuNjRDNS41NzkwMSAxNS42NCA3Ljk3OTAxIDE3Ljk5IDEwLjk5OSAxOC40MlYyMC41QzEwLjk5OSAyMS4wNSAxMS40NDkgMjEuNSAxMS45OTkgMjEuNUMxMi41NDkgMjEuNSAxMi45OTkgMjEuMDUgMTIuOTk5IDIwLjVWMTguNDJDMTYuMDE5IDE3Ljk5IDE4LjQxOSAxNS42NCAxOC45MDkgMTIuNjRDMTkuMDA5IDEyLjA0IDE4LjUxOSAxMS41IDE3LjkwOSAxMS41WiIgZmlsbD0iI0IwQjRCNyIvPgo8L3N2Zz4K'); + --str-video__icon--mic-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTE0LjkyMDQgMTEuMVY1LjVDMTQuOTIwNCAzLjg0IDEzLjU4MDQgMi41IDExLjkyMDQgMi41QzEwLjM4MDQgMi41IDkuMTMwMzkgMy42NiA4Ljk2MDM5IDUuMTVMMTQuOTIwNCAxMS4xWk0xOC4wMDA0IDExLjVDMTcuNTkwNCAxMS41IDE3LjIzMDQgMTEuOCAxNy4xNzA0IDEyLjIxQzE3LjEyMDQgMTIuNTMgMTcuMDUwNCAxMi44NSAxNi45NTA0IDEzLjE0TDE4LjIyMDQgMTQuNDFDMTguNTIwNCAxMy44MSAxOC43NDA0IDEzLjE2IDE4Ljg1MDQgMTIuNDdDMTguOTIwNCAxMS45NiAxOC41MjA0IDExLjUgMTguMDAwNCAxMS41Wk0zLjYzMDM5IDQuMDZDMy4yNDAzOSA0LjQ1IDMuMjQwMzkgNS4wOCAzLjYzMDM5IDUuNDdMOC45MjAzOSAxMC43N1YxMS4yQzguOTIwMzkgMTIuMzkgOS41MjAzOSAxMy41MiAxMC41NTA0IDE0LjExQzExLjMwMDQgMTQuNTQgMTEuOTYwNCAxNC41NSAxMi41NzA0IDE0LjQyTDE0LjIzMDQgMTYuMDhDMTMuNTIwNCAxNi40MSAxMi43MzA0IDE2LjYgMTEuOTIwNCAxNi42QzkuMzgwMzkgMTYuNiA3LjA0MDM5IDE0LjgzIDYuNjcwMzkgMTIuMjFDNi42MTAzOSAxMS44IDYuMjUwMzkgMTEuNSA1Ljg0MDM5IDExLjVDNS4zMjAzOSAxMS41IDQuOTIwMzkgMTEuOTYgNC45OTAzOSAxMi40N0M1LjQ1MDM5IDE1LjQzIDcuOTUwMzkgMTcuNzcgMTAuOTIwNCAxOC4yMlYyMC41QzEwLjkyMDQgMjEuMDUgMTEuMzcwNCAyMS41IDExLjkyMDQgMjEuNUMxMi40NzA0IDIxLjUgMTIuOTIwNCAyMS4wNSAxMi45MjA0IDIwLjVWMTguMjJDMTMuODMwNCAxOC4wOSAxNC42OTA0IDE3Ljc3IDE1LjQ3MDQgMTcuMzJMMTguOTYwNCAyMC44MUMxOS4zNTA0IDIxLjIgMTkuOTgwNCAyMS4yIDIwLjM3MDQgMjAuODFDMjAuNzYwNCAyMC40MiAyMC43NjA0IDE5Ljc5IDIwLjM3MDQgMTkuNEw1LjA0MDM5IDQuMDZDNC42NTAzOSAzLjY3IDQuMDIwMzkgMy42NyAzLjYzMDM5IDQuMDZaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--more: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTEyIDhDMTMuMSA4IDE0IDcuMSAxNCA2QzE0IDQuOSAxMy4xIDQgMTIgNEMxMC45IDQgMTAgNC45IDEwIDZDMTAgNy4xIDEwLjkgOCAxMiA4Wk0xMiAxMEMxMC45IDEwIDEwIDEwLjkgMTAgMTJDMTAgMTMuMSAxMC45IDE0IDEyIDE0QzEzLjEgMTQgMTQgMTMuMSAxNCAxMkMxNCAxMC45IDEzLjEgMTAgMTIgMTBaTTEyIDE2QzEwLjkgMTYgMTAgMTYuOSAxMCAxOEMxMCAxOS4xIDEwLjkgMjAgMTIgMjBDMTMuMSAyMCAxNCAxOS4xIDE0IDE4QzE0IDE2LjkgMTMuMSAxNiAxMiAxNloiIGZpbGw9IiNCMEI0QjciLz4KPC9nPgo8L3N2Zz4K'); + --str-video__icon--network-quality: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYuNCA5LjJINi42QzcuMzcgOS4yIDggOS44MyA4IDEwLjZWMTcuNkM4IDE4LjM3IDcuMzcgMTkgNi42IDE5SDYuNEM1LjYzIDE5IDUgMTguMzcgNSAxNy42VjEwLjZDNSA5LjgzIDUuNjMgOS4yIDYuNCA5LjJaTTEyIDVDMTIuNzcgNSAxMy40IDUuNjMgMTMuNCA2LjRWMTcuNkMxMy40IDE4LjM3IDEyLjc3IDE5IDEyIDE5QzExLjIzIDE5IDEwLjYgMTguMzcgMTAuNiAxNy42VjYuNEMxMC42IDUuNjMgMTEuMjMgNSAxMiA1Wk0xNy42IDEzQzE4LjM3IDEzIDE5IDEzLjYzIDE5IDE0LjRWMTcuNkMxOSAxOC4zNyAxOC4zNyAxOSAxNy42IDE5QzE2LjgzIDE5IDE2LjIgMTguMzcgMTYuMiAxNy42VjE0LjRDMTYuMiAxMy42MyAxNi44MyAxMyAxNy42IDEzWiIgZmlsbD0iI0UzRTRFNSIvPgo8L3N2Zz4K'); + --str-video__icon--participants: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTE2IDExQzE3LjY2IDExIDE4Ljk5IDkuNjYgMTguOTkgOEMxOC45OSA2LjM0IDE3LjY2IDUgMTYgNUMxNC4zNCA1IDEzIDYuMzQgMTMgOEMxMyA5LjY2IDE0LjM0IDExIDE2IDExWk04IDExQzkuNjYgMTEgMTAuOTkgOS42NiAxMC45OSA4QzEwLjk5IDYuMzQgOS42NiA1IDggNUM2LjM0IDUgNSA2LjM0IDUgOEM1IDkuNjYgNi4zNCAxMSA4IDExWk04IDEzQzUuNjcgMTMgMSAxNC4xNyAxIDE2LjVWMThDMSAxOC41NSAxLjQ1IDE5IDIgMTlIMTRDMTQuNTUgMTkgMTUgMTguNTUgMTUgMThWMTYuNUMxNSAxNC4xNyAxMC4zMyAxMyA4IDEzWk0xNiAxM0MxNS43MSAxMyAxNS4zOCAxMy4wMiAxNS4wMyAxMy4wNUMxNS4wNSAxMy4wNiAxNS4wNiAxMy4wOCAxNS4wNyAxMy4wOUMxNi4yMSAxMy45MiAxNyAxNS4wMyAxNyAxNi41VjE4QzE3IDE4LjM1IDE2LjkzIDE4LjY5IDE2LjgyIDE5SDIyQzIyLjU1IDE5IDIzIDE4LjU1IDIzIDE4VjE2LjVDMjMgMTQuMTcgMTguMzMgMTMgMTYgMTNaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--person-add: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTUiIHZpZXdCb3g9IjAgMCAxNCAxNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9InBlcnNvbl9hZGRfYWx0Ij4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTguOTc3NDIgOC42NDg1QzcuOTk3NDIgOC4xNDY4MyA2LjcyNTc1IDcuNzM4NSA1LjI0OTkyIDcuNzM4NUMzLjc3NDA5IDcuNzM4NSAyLjUwMjQyIDguMTQ2ODMgMS41MjI0MiA4LjY0ODVDMC45MzkwODUgOC45NDYgMC41ODMyNTIgOS41NDY4MyAwLjU4MzI1MiAxMC4yMDAyVjExLjgyMThIOS45MTY1OVYxMC4yMDAyQzkuOTE2NTkgOS41NDY4MyA5LjU2MDc1IDguOTQ2IDguOTc3NDIgOC42NDg1Wk01LjI0OTkyIDcuMTU1MTZDNi41MzkwOSA3LjE1NTE2IDcuNTgzMjUgNi4xMTEgNy41ODMyNSA0LjgyMTgzQzcuNTgzMjUgMy41MzI2NiA2LjUzOTA5IDIuNDg4NDkgNS4yNDk5MiAyLjQ4ODQ5QzMuOTYwNzUgMi40ODg0OSAyLjkxNjU5IDMuNTMyNjYgMi45MTY1OSA0LjgyMTgzQzIuOTE2NTkgNi4xMTEgMy45NjA3NSA3LjE1NTE2IDUuMjQ5OTIgNy4xNTUxNlpNMTEuNjY2NiA1LjQwNTE2VjQuMjM4NDlDMTEuNjY2NiAzLjkxNzY2IDExLjQwNDEgMy42NTUxNiAxMS4wODMzIDMuNjU1MTZDMTAuNzYyNCAzLjY1NTE2IDEwLjQ5OTkgMy45MTc2NiAxMC40OTk5IDQuMjM4NDlWNS40MDUxNkg5LjMzMzI1QzkuMDEyNDIgNS40MDUxNiA4Ljc0OTkyIDUuNjY3NjYgOC43NDk5MiA1Ljk4ODVDOC43NDk5MiA2LjMwOTMzIDkuMDEyNDIgNi41NzE4MyA5LjMzMzI1IDYuNTcxODNIMTAuNDk5OVY3LjczODVDMTAuNDk5OSA4LjA1OTMzIDEwLjc2MjQgOC4zMjE4MyAxMS4wODMzIDguMzIxODNDMTEuNDA0MSA4LjMyMTgzIDExLjY2NjYgOC4wNTkzMyAxMS42NjY2IDcuNzM4NVY2LjU3MTgzSDEyLjgzMzNDMTMuMTU0MSA2LjU3MTgzIDEzLjQxNjYgNi4zMDkzMyAxMy40MTY2IDUuOTg4NUMxMy40MTY2IDUuNjY3NjYgMTMuMTU0MSA1LjQwNTE2IDEyLjgzMzMgNS40MDUxNkgxMS42NjY2WiIgZmlsbD0iI0UzRTRFNSIvPgo8L2c+Cjwvc3ZnPgo='); + --str-video__icon--qr-code: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAyMyAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTkuMjA3MDMgNi4wNTg5M1Y4LjgwODkzSDYuNDU3MDNWNi4wNTg5M0g5LjIwNzAzWk0xMC41ODIgNC42ODM5M0g1LjA4MjAzVjEwLjE4MzlIMTAuNTgyVjQuNjgzOTNaTTkuMjA3MDMgMTMuMzkyM1YxNi4xNDIzSDYuNDU3MDNWMTMuMzkyM0g5LjIwNzAzWk0xMC41ODIgMTIuMDE3M0g1LjA4MjAzVjE3LjUxNzNIMTAuNTgyVjEyLjAxNzNaTTE2LjU0MDQgNi4wNTg5M1Y4LjgwODkzSDEzLjc5MDRWNi4wNTg5M0gxNi41NDA0Wk0xNy45MTU0IDQuNjgzOTNIMTIuNDE1NFYxMC4xODM5SDE3LjkxNTRWNC42ODM5M1pNMTIuNDE1NCAxMi4wMTczSDEzLjc5MDRWMTMuMzkyM0gxMi40MTU0VjEyLjAxNzNaTTEzLjc5MDQgMTMuMzkyM0gxNS4xNjU0VjE0Ljc2NzNIMTMuNzkwNFYxMy4zOTIzWk0xNS4xNjU0IDEyLjAxNzNIMTYuNTQwNFYxMy4zOTIzSDE1LjE2NTRWMTIuMDE3M1pNMTIuNDE1NCAxNC43NjczSDEzLjc5MDRWMTYuMTQyM0gxMi40MTU0VjE0Ljc2NzNaTTEzLjc5MDQgMTYuMTQyM0gxNS4xNjU0VjE3LjUxNzNIMTMuNzkwNFYxNi4xNDIzWk0xNS4xNjU0IDE0Ljc2NzNIMTYuNTQwNFYxNi4xNDIzSDE1LjE2NTRWMTQuNzY3M1pNMTYuNTQwNCAxMy4zOTIzSDE3LjkxNTRWMTQuNzY3M0gxNi41NDA0VjEzLjM5MjNaTTE2LjU0MDQgMTYuMTQyM0gxNy45MTU0VjE3LjUxNzNIMTYuNTQwNFYxNi4xNDIzWk0yMC42NjU0IDYuNTE3MjZIMTguODMyVjMuNzY3MjZIMTYuMDgyVjEuOTMzOTNIMjAuNjY1NFY2LjUxNzI2Wk0yMC42NjU0IDIwLjI2NzNWMTUuNjgzOUgxOC44MzJWMTguNDMzOUgxNi4wODJWMjAuMjY3M0gyMC42NjU0Wk0yLjMzMjAzIDIwLjI2NzNINi45MTUzNlYxOC40MzM5SDQuMTY1MzZWMTUuNjgzOUgyLjMzMjAzVjIwLjI2NzNaTTIuMzMyMDMgMS45MzM5M1Y2LjUxNzI2SDQuMTY1MzZWMy43NjcyNkg2LjkxNTM2VjEuOTMzOTNIMi4zMzIwM1oiIGZpbGw9IiNGQUZBRkEiLz4KPC9zdmc+Cg=='); + --str-video__icon--reactions: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTIzIDQuNUMyMyA1LjA1IDIyLjU1IDUuNSAyMiA1LjVIMjFWNi41QzIxIDcuMDUgMjAuNTUgNy41IDIwIDcuNUMxOS40NSA3LjUgMTkgNy4wNSAxOSA2LjVWNS41SDE4QzE3LjQ1IDUuNSAxNyA1LjA1IDE3IDQuNUMxNyAzLjk1IDE3LjQ1IDMuNSAxOCAzLjVIMTlWMi41QzE5IDEuOTUgMTkuNDUgMS41IDIwIDEuNUMyMC41NSAxLjUgMjEgMS45NSAyMSAyLjVWMy41SDIyQzIyLjU1IDMuNSAyMyAzLjk1IDIzIDQuNVpNMjAuNTIgOS40NUMyMC44MyAxMC40MSAyMSAxMS40NCAyMSAxMi41QzIxIDE4LjAyIDE2LjUyIDIyLjUgMTEgMjIuNUM1LjQ4IDIyLjUgMSAxOC4wMiAxIDEyLjVDMSA2Ljk4IDUuNDggMi41IDExIDIuNUMxMi41IDIuNSAxMy45MiAyLjg0IDE1LjIgMy40NEMxNS4wOCAzLjc3IDE1IDQuMTIgMTUgNC41QzE1IDUuODUgMTUuOSA3IDE3LjEzIDcuMzdDMTcuNSA4LjYgMTguNjUgOS41IDIwIDkuNUMyMC4xOCA5LjUgMjAuMzUgOS40OCAyMC41MiA5LjQ1Wk02IDEwQzYgMTAuODMgNi42NyAxMS41IDcuNSAxMS41QzguMzMgMTEuNSA5IDEwLjgzIDkgMTBDOSA5LjE3IDguMzMgOC41IDcuNSA4LjVDNi42NyA4LjUgNiA5LjE3IDYgMTBaTTE1LjMxIDE0LjVINi42OUM2LjMxIDE0LjUgNi4wNiAxNC45MiA2LjI1IDE1LjI1QzcuMiAxNi44OSA4Ljk3IDE4IDExIDE4QzEzLjAzIDE4IDE0LjggMTYuODkgMTUuNzUgMTUuMjVDMTUuOTQgMTQuOTIgMTUuNyAxNC41IDE1LjMxIDE0LjVaTTE2IDEwQzE2IDkuMTcgMTUuMzMgOC41IDE0LjUgOC41QzEzLjY3IDguNSAxMyA5LjE3IDEzIDEwQzEzIDEwLjgzIDEzLjY3IDExLjUgMTQuNSAxMS41QzE1LjMzIDExLjUgMTYgMTAuODMgMTYgMTBaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--recording-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPGcgaWQ9IlZlY3RvciI+CjxwYXRoIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMkMyIDE3LjUyIDYuNDggMjIgMTIgMjJDMTcuNTIgMjIgMjIgMTcuNTIgMjIgMTJDMjIgNi40OCAxNy41MiAyIDEyIDJaTTEyIDIwQzcuNTggMjAgNCAxNi40MiA0IDEyQzQgNy41OCA3LjU4IDQgMTIgNEMxNi40MiA0IDIwIDcuNTggMjAgMTJDMjAgMTYuNDIgMTYuNDIgMjAgMTIgMjBaIiBmaWxsPSIjQjBCNEI3Ii8+CjxwYXRoIGQ9Ik0xMiAxN0MxNC43NjE0IDE3IDE3IDE0Ljc2MTQgMTcgMTJDMTcgOS4yMzg1OCAxNC43NjE0IDcgMTIgN0M5LjIzODU4IDcgNyA5LjIzODU4IDcgMTJDNyAxNC43NjE0IDkuMjM4NTggMTcgMTIgMTdaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9nPgo8L3N2Zz4K'); + --str-video__icon--recording-on: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDBDNC40OCAwIDAgNC40OCAwIDEwQzAgMTUuNTIgNC40OCAyMCAxMCAyMEMxNS41MiAyMCAyMCAxNS41MiAyMCAxMEMyMCA0LjQ4IDE1LjUyIDAgMTAgMFpNMTMgMTRIN0M2LjQ1IDE0IDYgMTMuNTUgNiAxM1Y3QzYgNi40NSA2LjQ1IDYgNyA2SDEzQzEzLjU1IDYgMTQgNi40NSAxNCA3VjEzQzE0IDEzLjU1IDEzLjU1IDE0IDEzIDE0WiIgZmlsbD0iI0UzRTRFNSIvPgo8L3N2Zz4K'); --str-video__icon--refresh: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTIwIDE5MjAiPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTk2MCAwdjIxMy4zMzNjNDExLjYyNyAwIDc0Ni42NjcgMzM0LjkzNCA3NDYuNjY3IDc0Ni42NjdTMTM3MS42MjcgMTcwNi42NjcgOTYwIDE3MDYuNjY3IDIxMy4zMzMgMTM3MS43MzMgMjEzLjMzMyA5NjBjMC0xOTcuMDEzIDc4LjQtMzgyLjUwNyAyMTMuMzM0LTUyMC43NDd2MjU0LjA4SDY0MFYxMDYuNjY3SDUzLjMzM1YzMjBoMTkxLjA0Qzg4LjY0IDQ5NC4wOCAwIDcyMC45NiAwIDk2MGMwIDUyOS4yOCA0MzAuNjEzIDk2MCA5NjAgOTYwczk2MC00MzAuNzIgOTYwLTk2MFMxNDg5LjM4NyAwIDk2MCAwIi8+PC9zdmc+Cg=='); - --str-video__icon--screen-share-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAzMCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI1IDE3Ljc5NjlDMjYuMzc1IDE3Ljc5NjkgMjcuNDg3NSAxNi43MDcgMjcuNDg3NSAxNS4zNzVMMjcuNSAzLjI2NTYyQzI3LjUgMS45MjE0OCAyNi4zNzUgMC44NDM3NSAyNSAwLjg0Mzc1SDVDMy42MTI1IDAuODQzNzUgMi41IDEuOTIxNDggMi41IDMuMjY1NjJWMTUuMzc1QzIuNSAxNi43MDcgMy42MTI1IDE3Ljc5NjkgNSAxNy43OTY5SDBWMjAuMjE4OEgzMFYxNy43OTY5SDI1Wk01IDE1LjM3NVYzLjI2NTYySDI1VjE1LjM4NzFMNSAxNS4zNzVaTTE2LjI1IDcuMDU1ODZDMTEuMzg3NSA3LjcwOTc3IDkuNDUgMTAuOTMwOSA4Ljc1IDE0LjE2NDFDMTAuNDg3NSAxMS44OTk2IDEyLjc3NSAxMC44NzAzIDE2LjI1IDEwLjg3MDNWMTMuNTIyM0wyMS4yNSA4Ljk5MzM2TDE2LjI1IDQuNDc2NTZWNy4wNTU4NloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo='); - --str-video__icon--screen-share-on: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAzMCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI1IDE3Ljc5NjlDMjYuMzc1IDE3Ljc5NjkgMjcuNDg3NSAxNi43MDcgMjcuNDg3NSAxNS4zNzVMMjcuNSAzLjI2NTYyQzI3LjUgMS45MjE0OCAyNi4zNzUgMC44NDM3NSAyNSAwLjg0Mzc1SDVDMy42MTI1IDAuODQzNzUgMi41IDEuOTIxNDggMi41IDMuMjY1NjJWMTUuMzc1QzIuNSAxNi43MDcgMy42MTI1IDE3Ljc5NjkgNSAxNy43OTY5SDBWMjAuMjE4OEgzMFYxNy43OTY5SDI1Wk01IDE1LjM3NVYzLjI2NTYySDI1VjE1LjM4NzFMNSAxNS4zNzVaTTE2LjI1IDcuMDU1ODZDMTEuMzg3NSA3LjcwOTc3IDkuNDUgMTAuOTMwOSA4Ljc1IDE0LjE2NDFDMTAuNDg3NSAxMS44OTk2IDEyLjc3NSAxMC44NzAzIDE2LjI1IDEwLjg3MDNWMTMuNTIyM0wyMS4yNSA4Ljk5MzM2TDE2LjI1IDQuNDc2NTZWNy4wNTU4NloiIGZpbGw9IiMwMDVGRkYiLz4KPC9zdmc+Cg=='); - --str-video__icon--settings: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNOS4xMTAzNSAxOS4xOTA0SDEwLjg4NTdDMTEuNjE1MiAxOS4xOTA0IDEyLjE4NjUgMTguNzQyMiAxMi4zNTM1IDE4LjA0NzlMMTIuNzEzOSAxNi40OTIyTDEyLjk0MjQgMTYuNDA0M0wxNC4yOTU5IDE3LjIzOTNDMTQuOTExMSAxNy42MjYgMTUuNjMxOCAxNy41MjkzIDE2LjE0MTYgMTcuMDEwN0wxNy4zNzIxIDE1Ljc4MDNDMTcuODk5NCAxNS4yNjE3IDE3Ljk4NzMgMTQuNTQxIDE3LjYwMDYgMTMuOTM0NkwxNi43NjU2IDEyLjU4OThMMTYuODQ0NyAxMi4zNzAxTDE4LjQwMDQgMTIuMDA5OEMxOS4wODU5IDExLjgzNCAxOS41NDMgMTEuMjYyNyAxOS41NDMgMTAuNTQyVjguODEwNTVDMTkuNTQzIDguMDg5ODQgMTkuMDk0NyA3LjUxODU1IDE4LjQwMDQgNy4zNDI3N0wxNi44NjIzIDYuOTczNjNMMTYuNzc0NCA2Ljc0NTEyTDE3LjYwOTQgNS40MDAzOUMxNy45OTYxIDQuNzkzOTUgMTcuODk5NCA0LjA4MjAzIDE3LjM4MDkgMy41NDU5TDE2LjE1MDQgMi4zMTU0M0MxNS42NDA2IDEuODA1NjYgMTQuOTE5OSAxLjcwODk4IDE0LjMxMzUgMi4wODY5MUwxMi45NiAyLjkxMzA5TDEyLjcxMzkgMi44MTY0MUwxMi4zNTM1IDEuMjYwNzRDMTIuMTg2NSAwLjU2NjQwNiAxMS42MTUyIDAuMTE4MTY0IDEwLjg4NTcgMC4xMTgxNjRIOS4xMTAzNUM4LjM3MjA3IDAuMTE4MTY0IDcuODA5NTcgMC41NjY0MDYgNy42NDI1OCAxLjI2MDc0TDcuMjgyMjMgMi44MTY0MUw3LjAzNjEzIDIuOTEzMDlMNS42ODI2MiAyLjA4NjkxQzUuMDY3MzggMS43MDg5OCA0LjM0NjY4IDEuODA1NjYgMy44MzY5MSAyLjMxNTQzTDIuNjA2NDUgMy41NDU5QzIuMDg3ODkgNC4wODIwMyAxLjk5MTIxIDQuNzkzOTUgMi4zNzc5MyA1LjQwMDM5TDMuMjIxNjggNi43NDUxMkwzLjEyNSA2Ljk3MzYzTDEuNTk1NyA3LjM0Mjc3QzAuODkyNTc4IDcuNTE4NTUgMC40NTMxMjUgOC4wODk4NCAwLjQ1MzEyNSA4LjgxMDU1VjEwLjU0MkMwLjQ1MzEyNSAxMS4yNjI3IDAuOTAxMzY3IDExLjgzNCAxLjU5NTcgMTIuMDA5OEwzLjE0MjU4IDEyLjM3MDFMMy4yMzA0NyAxMi41ODk4TDIuMzg2NzIgMTMuOTM0NkMyIDE0LjU0MSAyLjA5NjY4IDE1LjI2MTcgMi42MTUyMyAxNS43ODAzTDMuODQ1NyAxNy4wMTA3QzQuMzY0MjYgMTcuNTI5MyA1LjA4NDk2IDE3LjYyNiA1LjY5MTQxIDE3LjIzOTNMNy4wNDQ5MiAxNi40MDQzTDcuMjgyMjMgMTYuNDkyMkw3LjY0MjU4IDE4LjA0NzlDNy44MDk1NyAxOC43NDIyIDguMzcyMDcgMTkuMTkwNCA5LjExMDM1IDE5LjE5MDRaTTkuMzIxMjkgMTcuNTk5NkM5LjE3MTg4IDE3LjU5OTYgOS4xMDE1NiAxNy41MjkzIDkuMDc1MiAxNy4zOTc1TDguNTU2NjQgMTUuMjM1NEM3Ljk5NDE0IDE1LjEwMzUgNy40NjY4IDE0Ljg4MzggNy4wMzYxMyAxNC42MTEzTDUuMTM3NyAxNS43ODAzQzUuMDMyMjMgMTUuODUwNiA0LjkyNjc2IDE1Ljg0MTggNC44MTI1IDE1LjczNjNMMy44ODA4NiAxNC44MDQ3QzMuNzg0MTggMTQuNzA4IDMuNzc1MzkgMTQuNjAyNSAzLjg1NDQ5IDE0LjQ4ODNMNS4wMjM0NCAxMi41ODk4QzQuNzc3MzQgMTIuMTc2OCA0LjU0MDA0IDExLjY0OTQgNC40MDgyIDExLjA5NTdMMi4yMzczIDEwLjU3NzFDMi4xMDU0NyAxMC41NTA4IDIuMDQzOTUgMTAuNDgwNSAyLjA0Mzk1IDEwLjMzMTFWOS4wMTI3QzIuMDQzOTUgOC44NTQ0OSAyLjEwNTQ3IDguNzkyOTcgMi4yMzczIDguNzU3ODFMNC40MDgyIDguMjQ4MDVDNC41NDAwNCA3LjY1OTE4IDQuNzk0OTIgNy4xMTQyNiA1LjAwNTg2IDYuNzM2MzNMMy44NDU3IDQuODQ2NjhDMy43NjY2IDQuNzIzNjMgMy43NjY2IDQuNjE4MTYgMy44NzIwNyA0LjUxMjdMNC44MDM3MSAzLjU4OTg0QzQuOTE3OTcgMy40OTMxNiA1LjAwNTg2IDMuNDc1NTkgNS4xMzc3IDMuNTQ1OUw3LjAyNzM0IDQuNjk3MjdDNy40MjI4NSA0LjQ1OTk2IDguMDAyOTMgNC4yMTM4NyA4LjU1NjY0IDQuMDczMjRMOS4wNzUyIDEuOTExMTNDOS4xMDE1NiAxLjc3OTMgOS4xNzE4OCAxLjcwODk4IDkuMzIxMjkgMS43MDg5OEgxMC42NzQ4QzEwLjgyNDIgMS43MDg5OCAxMC44OTQ1IDEuNzcwNTEgMTAuOTEyMSAxLjkxMTEzTDExLjQzOTUgNC4wOTA4MkMxMi4wMTA3IDQuMjIyNjYgMTIuNTI5MyA0LjQ1OTk2IDEyLjk2IDQuNzA2MDVMMTQuODQ5NiAzLjU1NDY5QzE0Ljk4MTQgMy40ODQzOCAxNS4wNjkzIDMuNDkzMTYgMTUuMTc0OCAzLjU5ODYzTDE2LjExNTIgNC41MjE0OEMxNi4yMjA3IDQuNjE4MTYgMTYuMjIwNyA0LjcyMzYzIDE2LjE0MTYgNC44NDY2OEwxNC45ODE0IDYuNzM2MzNDMTUuMTkyNCA3LjExNDI2IDE1LjQ1NjEgNy42NTkxOCAxNS41ODc5IDguMjQ4MDVMMTcuNzUgOC43NTc4MUMxNy44OTA2IDguNzkyOTcgMTcuOTUyMSA4Ljg1NDQ5IDE3Ljk1MjEgOS4wMTI3VjEwLjMzMTFDMTcuOTUyMSAxMC40ODA1IDE3Ljg4MTggMTAuNTUwOCAxNy43NSAxMC41NzcxTDE1LjU3OTEgMTEuMDk1N0MxNS40NDczIDExLjY0OTQgMTUuMjEgMTIuMTc2OCAxNC45NzI3IDEyLjU4OThMMTYuMTMyOCAxNC40ODgzQzE2LjIwMzEgMTQuNjAyNSAxNi4yMDMxIDE0LjcwOCAxNi4xMDY0IDE0Ljc5NTlMMTUuMTc0OCAxNS43MzYzQzE1LjA2MDUgMTUuODQxOCAxNC45NTUxIDE1Ljg1MDYgMTQuODQ5NiAxNS43ODAzTDEyLjk1MTIgMTQuNjExM0MxMi41MjA1IDE0Ljg4MzggMTIuMDE5NSAxNS4xMDM1IDExLjQzOTUgMTUuMjM1NEwxMC45MTIxIDE3LjM5NzVDMTAuODk0NSAxNy41MzgxIDEwLjgyNDIgMTcuNTk5NiAxMC42NzQ4IDE3LjU5OTZIOS4zMjEyOVpNOS45OTgwNSAxMi45OTQxQzExLjgyNjIgMTIuOTk0MSAxMy4zMjkxIDExLjQ5MTIgMTMuMzI5MSA5LjY1NDNDMTMuMzI5MSA3LjgzNDk2IDExLjgyNjIgNi4zMzIwMyA5Ljk5ODA1IDYuMzMyMDNDOC4xNjk5MiA2LjMzMjAzIDYuNjU4MiA3LjgzNDk2IDYuNjU4MiA5LjY1NDNDNi42NTgyIDExLjQ5MTIgOC4xNjExMyAxMi45OTQxIDkuOTk4MDUgMTIuOTk0MVpNOS45OTgwNSAxMS40OTEyQzguOTk2MDkgMTEuNDkxMiA4LjE2OTkyIDEwLjY2NSA4LjE2OTkyIDkuNjU0M0M4LjE2OTkyIDguNjYxMTMgOC45OTYwOSA3Ljg0Mzc1IDkuOTk4MDUgNy44NDM3NUMxMC45ODI0IDcuODQzNzUgMTEuNzk5OCA4LjY2MTEzIDExLjc5OTggOS42NTQzQzExLjc5OTggMTAuNjU2MiAxMC45ODI0IDExLjQ5MTIgOS45OTgwNSAxMS40OTEyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=='); - --str-video__icon--speaker: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAyMyAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuMjUgNy44OTg0OVYxNS4xNjQxSDUuMjVMMTEuNSAyMS4yMTg4VjEuODQzOEw1LjI1IDcuODk4NDlIMC4yNVpNMTcuMTI1IDExLjUzMTNDMTcuMTI1IDkuMzg3OTQgMTUuODUgNy41NDczMSAxNCA2LjY1MTIyVjE2LjM5OTNDMTUuODUgMTUuNTE1MyAxNy4xMjUgMTMuNjc0NyAxNy4xMjUgMTEuNTMxM1pNMTQgMC45MTEzNzdWMy40MDU5MUMxNy42MTI1IDQuNDQ3MzEgMjAuMjUgNy42OTI2MyAyMC4yNSAxMS41MzEzQzIwLjI1IDE1LjM3IDE3LjYxMjUgMTguNjE1MyAxNCAxOS42NTY3VjIyLjE1MTJDMTkuMDEyNSAyMS4wNDkzIDIyLjc1IDE2LjcxNDEgMjIuNzUgMTEuNTMxM0MyMi43NSA2LjM0ODQ5IDE5LjAxMjUgMi4wMTMzMyAxNCAwLjkxMTM3N1oiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo='); - --str-video__icon--stats: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIHZpZXdCb3g9IjAgMCAyNSAyNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIxLjA3NDIgNi4yMDgyOEwxMi4wNzQyIDEyLjczODNMNy42OTQyMiA4LjM1ODI4TDMuMDc0MjIgMTEuNzM4M1Y5LjI1ODI4TDcuOTA0MjIgNS43MzgyOEwxMi4yODQyIDEwLjExODNMMjEuMDc0MiAzLjczODI4VjYuMjA4MjhaTTIxLjA3NDIgMTUuNzM4M0gxNi4zNzQyTDEyLjIwNDIgMTkuMDc4M0w2LjA3NDIyIDEzLjE0ODNMMy4wNzQyMiAxNS4yNzgzVjE3LjczODNMNS44NzQyMiAxNS43MzgzTDEyLjA3NDIgMjEuNzM4M0wxNy4wNzQyIDE3LjczODNIMjEuMDc0MlYxNS43MzgzWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=='); + --str-video__icon--screen-share-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTIwIDE4QzIxLjEgMTggMjIgMTcuMSAyMiAxNlY2QzIyIDQuOSAyMS4xIDQgMjAgNEg0QzIuOSA0IDIgNC45IDIgNlYxNkMyIDE3LjEgMi44OSAxOCA0IDE4SDFDMC40NSAxOCAwIDE4LjQ1IDAgMTlDMCAxOS41NSAwLjQ1IDIwIDEgMjBIMjNDMjMuNTUgMjAgMjQgMTkuNTUgMjQgMTlDMjQgMTguNDUgMjMuNTUgMTggMjMgMThIMjBaTTEzIDE0LjQ3VjEyLjI4QzEwLjIyIDEyLjI4IDguMzkgMTMuMTMgNyAxNUM3LjU2IDEyLjMzIDkuMTEgOS42NyAxMyA5LjEzVjdMMTYuNjEgMTAuMzZDMTYuODIgMTAuNTYgMTYuODIgMTAuODkgMTYuNjEgMTEuMDlMMTMgMTQuNDdaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--screen-share-on: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPU91dGxpbmVkIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTIwIDE4QzIxLjEgMTggMjEuOTkgMTcuMSAyMS45OSAxNkwyMiA2QzIyIDQuODkgMjEuMSA0IDIwIDRINEMyLjg5IDQgMiA0Ljg5IDIgNlYxNkMyIDE3LjEgMi44OSAxOCA0IDE4SDBWMjBIMjRWMThIMjBaTTQgMTZWNkgyMFYxNi4wMUw0IDE2Wk0xMyA5LjEzQzkuMTEgOS42NyA3LjU2IDEyLjMzIDcgMTVDOC4zOSAxMy4xMyAxMC4yMiAxMi4yOCAxMyAxMi4yOFYxNC40N0wxNyAxMC43M0wxMyA3VjkuMTNaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--settings: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTE5LjQ5OTcgMTJDMTkuNDk5NyAxMS43NyAxOS40ODk3IDExLjU1IDE5LjQ2OTcgMTEuMzJMMjEuMzI5NyA5LjkxQzIxLjcyOTcgOS42MSAyMS44Mzk3IDkuMDUgMjEuNTg5NyA4LjYxTDE5LjcxOTcgNS4zOEMxOS40Njk3IDQuOTQgMTguOTI5NyA0Ljc2IDE4LjQ2OTcgNC45NkwxNi4zMTk3IDUuODdDMTUuOTQ5NyA1LjYxIDE1LjU1OTcgNS4zOCAxNS4xNDk3IDUuMTlMMTQuODU5NyAyLjg4QzE0Ljc5OTcgMi4zOCAxNC4zNjk3IDIgMTMuODY5NyAySDEwLjEzOTdDOS42Mjk2NyAyIDkuMTk5NjcgMi4zOCA5LjEzOTY3IDIuODhMOC44NDk2NyA1LjE5QzguNDM5NjcgNS4zOCA4LjA0OTY2IDUuNjEgNy42Nzk2NyA1Ljg3TDUuNTI5NjcgNC45NkM1LjA2OTY3IDQuNzYgNC41Mjk2NyA0Ljk0IDQuMjc5NjcgNS4zOEwyLjQwOTY3IDguNjJDMi4xNTk2NyA5LjA2IDIuMjY5NjcgOS42MSAyLjY2OTY3IDkuOTJMNC41Mjk2NyAxMS4zM0M0LjUwOTY3IDExLjU1IDQuNDk5NjcgMTEuNzcgNC40OTk2NyAxMkM0LjQ5OTY3IDEyLjIzIDQuNTA5NjcgMTIuNDUgNC41Mjk2NyAxMi42OEwyLjY2OTY3IDE0LjA5QzIuMjY5NjcgMTQuMzkgMi4xNTk2NyAxNC45NSAyLjQwOTY3IDE1LjM5TDQuMjc5NjcgMTguNjJDNC41Mjk2NyAxOS4wNiA1LjA2OTY3IDE5LjI0IDUuNTI5NjcgMTkuMDRMNy42Nzk2NyAxOC4xM0M4LjA0OTY2IDE4LjM5IDguNDM5NjcgMTguNjIgOC44NDk2NyAxOC44MUw5LjEzOTY3IDIxLjEyQzkuMTk5NjcgMjEuNjIgOS42Mjk2NyAyMiAxMC4xMjk3IDIySDEzLjg1OTdDMTQuMzU5NyAyMiAxNC43ODk3IDIxLjYyIDE0Ljg0OTcgMjEuMTJMMTUuMTM5NyAxOC44MUMxNS41NDk3IDE4LjYyIDE1LjkzOTcgMTguMzkgMTYuMzA5NyAxOC4xM0wxOC40NTk3IDE5LjA0QzE4LjkxOTcgMTkuMjQgMTkuNDU5NyAxOS4wNiAxOS43MDk3IDE4LjYyTDIxLjU3OTcgMTUuMzlDMjEuODI5NyAxNC45NSAyMS43MTk3IDE0LjQgMjEuMzE5NyAxNC4wOUwxOS40NTk3IDEyLjY4QzE5LjQ4OTcgMTIuNDUgMTkuNDk5NyAxMi4yMyAxOS40OTk3IDEyWk0xMi4wMzk3IDE1LjVDMTAuMTA5NyAxNS41IDguNTM5NjcgMTMuOTMgOC41Mzk2NyAxMkM4LjUzOTY3IDEwLjA3IDEwLjEwOTcgOC41IDEyLjAzOTcgOC41QzEzLjk2OTcgOC41IDE1LjUzOTcgMTAuMDcgMTUuNTM5NyAxMkMxNS41Mzk3IDEzLjkzIDEzLjk2OTcgMTUuNSAxMi4wMzk3IDE1LjVaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--speaker: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTMgMTAuMDAxOFYxNC4wMDE4QzMgMTQuNTUxOCAzLjQ1IDE1LjAwMTggNCAxNS4wMDE4SDdMMTAuMjkgMTguMjkxOEMxMC45MiAxOC45MjE4IDEyIDE4LjQ3MTggMTIgMTcuNTgxOFY2LjQxMTc5QzEyIDUuNTIxNzkgMTAuOTIgNS4wNzE3OSAxMC4yOSA1LjcwMTc5TDcgOS4wMDE3OUg0QzMuNDUgOS4wMDE3OSAzIDkuNDUxNzkgMyAxMC4wMDE4Wk0xNi41IDEyLjAwMThDMTYuNSAxMC4yMzE4IDE1LjQ4IDguNzExNzkgMTQgNy45NzE3OVYxNi4wMjE4QzE1LjQ4IDE1LjI5MTggMTYuNSAxMy43NzE4IDE2LjUgMTIuMDAxOFpNMTQgNC40NTE3OVY0LjY1MTc5QzE0IDUuMDMxNzkgMTQuMjUgNS4zNjE3OSAxNC42IDUuNTAxNzlDMTcuMTggNi41MzE3OSAxOSA5LjA2MTc5IDE5IDEyLjAwMThDMTkgMTQuOTQxOCAxNy4xOCAxNy40NzE4IDE0LjYgMTguNTAxOEMxNC4yNCAxOC42NDE4IDE0IDE4Ljk3MTggMTQgMTkuMzUxOFYxOS41NTE4QzE0IDIwLjE4MTggMTQuNjMgMjAuNjIxOCAxNS4yMSAyMC40MDE4QzE4LjYgMTkuMTExOCAyMSAxNS44NDE4IDIxIDEyLjAwMThDMjEgOC4xNjE3OSAxOC42IDQuODkxNzkgMTUuMjEgMy42MDE3OUMxNC42MyAzLjM3MTc5IDE0IDMuODIxNzkgMTQgNC40NTE3OVoiIGZpbGw9IiNCMEI0QjciLz4KPC9nPgo8L3N2Zz4K'); + --str-video__icon--stats: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTEzLjg5MTEgMTAuNTQ3NUwxMi44MzExIDEwLjA1NzVDMTIuNDQxMSA5Ljg3NzQ2IDEyLjQ0MTEgOS4zMjc0NiAxMi44MzExIDkuMTQ3NDZMMTMuODkxMSA4LjY1NzQ2TDE0LjM4MTEgNy42MDc0NkMxNC41NjExIDcuMjE3NDYgMTUuMTExMSA3LjIxNzQ2IDE1LjI5MTEgNy42MDc0NkwxNS43ODExIDguNjY3NDZMMTYuODMxMSA5LjE1NzQ2QzE3LjIyMTEgOS4zMzc0NiAxNy4yMjExIDkuODg3NDYgMTYuODMxMSAxMC4wNjc1TDE1Ljc3MTEgMTAuNTU3NUwxNS4yODExIDExLjYwNzVDMTUuMTAxMSAxMS45OTc1IDE0LjU1MTEgMTEuOTk3NSAxNC4zNzExIDExLjYwNzVMMTMuODkxMSAxMC41NDc1Wk00LjI4MTA3IDEzLjYwNzVMNC43NzEwNyAxMi41NDc1TDUuODMxMDcgMTIuMDU3NUM2LjIyMTA3IDExLjg3NzUgNi4yMjEwNyAxMS4zMjc1IDUuODMxMDcgMTEuMTQ3NUw0Ljc3MTA3IDEwLjY1NzVMNC4yODEwNyA5LjYwNzQ2QzQuMTExMDcgOS4yMTc0NiAzLjU1MTA3IDkuMjE3NDYgMy4zODEwNyA5LjYwNzQ2TDIuODkxMDcgMTAuNjY3NUwxLjgzMTA3IDExLjE1NzVDMS40NDEwNyAxMS4zMzc1IDEuNDQxMDcgMTEuODg3NSAxLjgzMTA3IDEyLjA2NzVMMi44OTEwNyAxMi41NTc1TDMuMzgxMDcgMTMuNjA3NUMzLjU1MTA3IDEzLjk5NzUgNC4xMTEwNyAxMy45OTc1IDQuMjgxMDcgMTMuNjA3NVpNOC43OTEwNyA4LjU5NzQ2TDkuNDIxMDcgNy4xOTc0NkwxMC44MjExIDYuNTY3NDZDMTEuMjExMSA2LjM4NzQ2IDExLjIxMTEgNS44Mzc0NiAxMC44MjExIDUuNjU3NDZMOS40MjEwNyA1LjAyNzQ2TDguNzkxMDcgMy42Mjc0NkM4LjYxMTA3IDMuMjM3NDYgOC4wNjEwNyAzLjIzNzQ2IDcuODgxMDcgMy42Mjc0Nkw3LjI1MTA3IDUuMDI3NDZMNS44NTEwNyA1LjY1NzQ2QzUuNDYxMDcgNS44Mzc0NiA1LjQ2MTA3IDYuMzg3NDYgNS44NTEwNyA2LjU2NzQ2TDcuMjUxMDcgNy4xOTc0Nkw3Ljg4MTA3IDguNTk3NDZDOC4wNTEwNyA4Ljk4NzQ2IDguNjExMDcgOC45ODc0NiA4Ljc5MTA3IDguNTk3NDZaTTIyLjE3MTEgOC44Nzc0NkMyMS43NzExIDguNDc3NDYgMjEuMTAxMSA4LjQ4NzQ2IDIwLjcyMTEgOC45MTc0NkwxNC4zMzExIDE2LjA5NzVMMTEuMDQxMSAxMi44MDc1QzEwLjY1MTEgMTIuNDE3NSAxMC4wMjExIDEyLjQxNzUgOS42MzEwNyAxMi44MDc1TDMuNTkxMDcgMTguODU3NUMzLjE4MTA3IDE5LjI2NzUgMy4xODEwNyAxOS45NDc1IDMuNTkxMDcgMjAuMzU3NUM0LjAwMTA3IDIwLjc2NzUgNC42ODEwNyAyMC43Njc1IDUuMDkxMDcgMjAuMzU3NUwxMC4zNDExIDE1LjA5NzVMMTMuNTkxMSAxOC4zNDc1QzE0LjAwMTEgMTguNzU3NSAxNC42NjExIDE4LjczNzUgMTUuMDQxMSAxOC4zMDc1TDIyLjIxMTEgMTAuMjM3NUMyMi41NjExIDkuODQ3NDYgMjIuNTQxMSA5LjI0NzQ2IDIyLjE3MTEgOC44Nzc0NloiIGZpbGw9IiNCMEI0QjciLz4KPC9nPgo8L3N2Zz4K'); + --str-video__icon--star: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjYiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNiAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE1LjA4NzggOS42MTE1NUwxMy41NTY1IDQuNzcxNTVDMTMuMjU0NSAzLjgyMTU1IDExLjg1ODYgMy44MjE1NSAxMS41NjcgNC43NzE1NUwxMC4wMjUzIDkuNjExNTVINS4zODk4OEM0LjM3OTQ2IDkuNjExNTUgMy45NjI4IDEwLjg2MTYgNC43ODU3MSAxMS40MjE2TDguNTc3MzggMTQuMDIxNkw3LjA4NzggMTguNjMxNkM2Ljc4NTcxIDE5LjU2MTYgNy45MTA3MSAyMC4zMTE2IDguNzEyOCAxOS43MjE2TDEyLjU1NjUgMTYuOTIxNkwxNi40MDAzIDE5LjczMTZDMTcuMjAyNCAyMC4zMjE2IDE4LjMyNzQgMTkuNTcxNiAxOC4wMjUzIDE4LjY0MTZMMTYuNTM1NyAxNC4wMzE2TDIwLjMyNzQgMTEuNDMxNkMyMS4xNTAzIDEwLjg2MTYgMjAuNzMzNiA5LjYyMTU1IDE5LjcyMzIgOS42MjE1NUgxNS4wODc4VjkuNjExNTVaIiBmaWxsPSIjRkZENjQ2Ii8+Cjwvc3ZnPgo='); + --str-video__icon--support-agent: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE3LjUwMTMgMTAuMTgzM0MxNy41MDEzIDUuNjA4MzMgMTMuOTUxMyAyLjUgMTAuMDAxMyAyLjVDNi4wOTI5NyAyLjUgMi41MDEzIDUuNTQxNjcgMi41MDEzIDEwLjIzMzNDMi4wMDEzIDEwLjUxNjcgMS42Njc5NyAxMS4wNSAxLjY2Nzk3IDExLjY2NjdWMTMuMzMzM0MxLjY2Nzk3IDE0LjI1IDIuNDE3OTcgMTUgMy4zMzQ2NCAxNUMzLjc5Mjk3IDE1IDQuMTY3OTcgMTQuNjI1IDQuMTY3OTcgMTQuMTY2N1YxMC4xNTgzQzQuMTY3OTcgNi45NjY2NyA2LjYyNjMgNC4xNzUgOS44MTc5NyA0LjA4MzMzQzEzLjExOCAzLjk4MzMzIDE1LjgzNDYgNi42MzMzMyAxNS44MzQ2IDkuOTE2NjdWMTUuODMzM0gxMC4wMDEzQzkuNTQyOTcgMTUuODMzMyA5LjE2Nzk3IDE2LjIwODMgOS4xNjc5NyAxNi42NjY3QzkuMTY3OTcgMTcuMTI1IDkuNTQyOTcgMTcuNSAxMC4wMDEzIDE3LjVIMTUuODM0NkMxNi43NTEzIDE3LjUgMTcuNTAxMyAxNi43NSAxNy41MDEzIDE1LjgzMzNWMTQuODE2N0MxNy45OTMgMTQuNTU4MyAxOC4zMzQ2IDE0LjA1IDE4LjMzNDYgMTMuNDVWMTEuNTMzM0MxOC4zMzQ2IDEwLjk1IDE3Ljk5MyAxMC40NDE3IDE3LjUwMTMgMTAuMTgzM1oiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTcuNTAxMyAxMS42NjY3QzcuOTYxNTQgMTEuNjY2NyA4LjMzNDY0IDExLjI5MzYgOC4zMzQ2NCAxMC44MzMzQzguMzM0NjQgMTAuMzczMSA3Ljk2MTU0IDEwIDcuNTAxMyAxMEM3LjA0MTA3IDEwIDYuNjY3OTcgMTAuMzczMSA2LjY2Nzk3IDEwLjgzMzNDNi42Njc5NyAxMS4yOTM2IDcuMDQxMDcgMTEuNjY2NyA3LjUwMTMgMTEuNjY2N1oiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTEyLjUwMTMgMTEuNjY2N0MxMi45NjE1IDExLjY2NjcgMTMuMzM0NiAxMS4yOTM2IDEzLjMzNDYgMTAuODMzM0MxMy4zMzQ2IDEwLjM3MzEgMTIuOTYxNSAxMCAxMi41MDEzIDEwQzEyLjA0MTEgMTAgMTEuNjY4IDEwLjM3MzEgMTEuNjY4IDEwLjgzMzNDMTEuNjY4IDExLjI5MzYgMTIuMDQxMSAxMS42NjY3IDEyLjUwMTMgMTEuNjY2N1oiIGZpbGw9IiNFM0U0RTUiLz4KPHBhdGggZD0iTTE1LjAwMTMgOS4xOTE2N0MxNC42MDEzIDYuODE2NjcgMTIuNTM0NiA1IDEwLjA0MyA1QzcuNTE3OTcgNSA0LjgwMTMgNy4wOTE2NyA1LjAxNzk3IDEwLjM3NUM3LjA3NjMgOS41MzMzMyA4LjYyNjMgNy43IDkuMDY3OTcgNS40NjY2N0MxMC4xNTk2IDcuNjU4MzMgMTIuNDAxMyA5LjE2NjY3IDE1LjAwMTMgOS4xOTE2N1oiIGZpbGw9IiNFM0U0RTUiLz4KPC9zdmc+Cg=='); --str-video__icon--user-plus: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUiIGhlaWdodD0iMTEiIHZpZXdCb3g9IjAgMCAxNSAxMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xMi40OTkgMy42MjQwMlYxLjc0OTAySDExLjI0OVYzLjYyNDAySDkuMzc0MDJWNC44NzQwMkgxMS4yNDlWNi43NDkwMkgxMi40OTlWNC44NzQwMkgxNC4zNzRWMy42MjQwMkgxMi40OTlaTTUuNjI0MDIgNS40OTkwMkM3LjAwNTI3IDUuNDk5MDIgOC4xMjQwMiA0LjM4MDI3IDguMTI0MDIgMi45OTkwMkM4LjEyNDAyIDEuNjE3NzcgNy4wMDUyNyAwLjQ5OTAyMyA1LjYyNDAyIDAuNDk5MDIzQzQuMjQyNzcgMC40OTkwMjMgMy4xMjQwMiAxLjYxNzc3IDMuMTI0MDIgMi45OTkwMkMzLjEyNDAyIDQuMzgwMjcgNC4yNDI3NyA1LjQ5OTAyIDUuNjI0MDIgNS40OTkwMlpNNS42MjQwMiAxLjc0OTAyQzYuMzExNTIgMS43NDkwMiA2Ljg3NDAyIDIuMzExNTIgNi44NzQwMiAyLjk5OTAyQzYuODc0MDIgMy42ODY1MiA2LjMxMTUyIDQuMjQ5MDIgNS42MjQwMiA0LjI0OTAyQzQuOTM2NTIgNC4yNDkwMiA0LjM3NDAyIDMuNjg2NTIgNC4zNzQwMiAyLjk5OTAyQzQuMzc0MDIgMi4zMTE1MiA0LjkzNjUyIDEuNzQ5MDIgNS42MjQwMiAxLjc0OTAyWk05LjYxNzc3IDcuMDk5MDJDOC41Njc3NyA2LjU2MTUyIDcuMjA1MjcgNi4xMjQwMiA1LjYyNDAyIDYuMTI0MDJDNC4wNDI3NyA2LjEyNDAyIDIuNjgwMjcgNi41NjE1MiAxLjYzMDI3IDcuMDk5MDJDMS4wMDUyNyA3LjQxNzc3IDAuNjI0MDIzIDguMDYxNTIgMC42MjQwMjMgOC43NjE1MlYxMC40OTlIMTAuNjI0VjguNzYxNTJDMTAuNjI0IDguMDYxNTIgMTAuMjQyOCA3LjQxNzc3IDkuNjE3NzcgNy4wOTkwMlpNOS4zNzQwMiA5LjI0OTAySDEuODc0MDJWOC43NjE1MkMxLjg3NDAyIDguNTI0MDIgMS45OTkwMiA4LjMxMTUyIDIuMTk5MDIgOC4yMTE1MkMyLjk0Mjc3IDcuODMwMjcgNC4xNDI3NyA3LjM3NDAyIDUuNjI0MDIgNy4zNzQwMkM3LjEwNTI3IDcuMzc0MDIgOC4zMDUyNyA3LjgzMDI3IDkuMDQ5MDIgOC4yMTE1MkM5LjI0OTAyIDguMzExNTIgOS4zNzQwMiA4LjUyNDAyIDkuMzc0MDIgOC43NjE1MlY5LjI0OTAyWiIgZmlsbD0iI0Y3RjdGOCIvPgo8L3N2Zz4K'); - --str-video__icon--pin: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTMiIGhlaWdodD0iMTciIHZpZXdCb3g9IjAgMCAxMyAxNyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTguMDY4MTkgMi40NTIyMlY2LjQ2MzYzQzguMDY4MTkgNy4zNjIxOCA4LjM2NTA0IDguMTk2NTYgOC44NzA0NyA4Ljg3MDQ3SDQuMDU2NzhDNC41NzgyNyA4LjE4MDUxIDQuODU5MDYgNy4zNDYxNCA0Ljg1OTA2IDYuNDYzNjNWMi40NTIyMkg4LjA2ODE5Wk0xMC40NzUgMC44NDc2NTZIMi40NTIyMkMyLjAxMDk2IDAuODQ3NjU2IDEuNjQ5OTQgMS4yMDg2OCAxLjY0OTk0IDEuNjQ5OTRDMS42NDk5NCAyLjA5MTE5IDIuMDEwOTYgMi40NTIyMiAyLjQ1MjIyIDIuNDUyMjJIMy4yNTQ1VjYuNDYzNjNDMy4yNTQ1IDcuNzk1NDEgMi4xNzk0NCA4Ljg3MDQ3IDAuODQ3NjU2IDguODcwNDdWMTAuNDc1SDUuNjM3MjhWMTYuMDkxTDYuNDM5NTYgMTYuODkzM0w3LjI0MTg0IDE2LjA5MVYxMC40NzVIMTIuMDc5NlY4Ljg3MDQ3QzEwLjc0NzggOC44NzA0NyA5LjY3Mjc1IDcuNzk1NDEgOS42NzI3NSA2LjQ2MzYzVjIuNDUyMjJIMTAuNDc1QzEwLjkxNjMgMi40NTIyMiAxMS4yNzczIDIuMDkxMTkgMTEuMjc3MyAxLjY0OTk0QzExLjI3NzMgMS4yMDg2OCAxMC45MTYzIDAuODQ3NjU2IDEwLjQ3NSAwLjg0NzY1NloiIGZpbGw9IiM3Mjc2N0UiLz4KPC9zdmc+Cg=='); + --str-video__icon--verified: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTkuMzI1IDEuMDU1MjVMMy40OTE2NyAzLjY0NjkyQzIuODkxNjcgMy45MTM1OCAyLjUgNC41MTM1OCAyLjUgNS4xNzE5MlY5LjA4ODU4QzIuNSAxMy43MTM2IDUuNyAxOC4wMzg2IDEwIDE5LjA4ODZDMTQuMyAxOC4wMzg2IDE3LjUgMTMuNzEzNiAxNy41IDkuMDg4NThWNS4xNzE5MkMxNy41IDQuNTEzNTggMTcuMTA4MyAzLjkxMzU4IDE2LjUwODMgMy42NDY5MkwxMC42NzUgMS4wNTUyNUMxMC4yNSAwLjg2MzU4MiA5Ljc1IDAuODYzNTgyIDkuMzI1IDEuMDU1MjVaTTcuNzQxNjcgMTMuNDk2OUw1LjU4MzMzIDExLjMzODZDNS4yNTgzMyAxMS4wMTM2IDUuMjU4MzMgMTAuNDg4NiA1LjU4MzMzIDEwLjE2MzZDNS45MDgzMyA5LjgzODU4IDYuNDMzMzMgOS44Mzg1OCA2Ljc1ODMzIDEwLjE2MzZMOC4zMzMzMyAxMS43MzAyTDEzLjIzMzMgNi44MzAyNUMxMy41NTgzIDYuNTA1MjUgMTQuMDgzMyA2LjUwNTI1IDE0LjQwODMgNi44MzAyNUMxNC43MzMzIDcuMTU1MjUgMTQuNzMzMyA3LjY4MDI1IDE0LjQwODMgOC4wMDUyNUw4LjkxNjY3IDEzLjQ5NjlDOC42IDEzLjgyMTkgOC4wNjY2NyAxMy44MjE5IDcuNzQxNjcgMTMuNDk2OVoiIGZpbGw9IiMwMEUyQTEiLz4KPC9zdmc+Cg=='); + --str-video__icon--paperclip: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgaWQ9IlN0eWxlPVJvdW5kIj4KPHBhdGggaWQ9IlZlY3RvciIgZD0iTTE2IDYuNzQ3NThWMTcuMzI3NkMxNiAxOS40MTc2IDE0LjQ3IDIxLjI3NzYgMTIuMzkgMjEuNDc3NkMxMCAyMS43MDc2IDggMTkuODM3NiA4IDE3LjQ5NzZWNS4xMzc1OEM4IDMuODI3NTggOC45NCAyLjYzNzU4IDEwLjI0IDIuNTA3NThDMTEuNzQgMi4zNTc1OCAxMyAzLjUyNzU4IDEzIDQuOTk3NThWMTUuNDk3NkMxMyAxNi4wNDc2IDEyLjU1IDE2LjQ5NzYgMTIgMTYuNDk3NkMxMS40NSAxNi40OTc2IDExIDE2LjA0NzYgMTEgMTUuNDk3NlY2Ljc0NzU4QzExIDYuMzM3NTggMTAuNjYgNS45OTc1OCAxMC4yNSA1Ljk5NzU4QzkuODQgNS45OTc1OCA5LjUgNi4zMzc1OCA5LjUgNi43NDc1OFYxNS4zNTc2QzkuNSAxNi42Njc2IDEwLjQ0IDE3Ljg1NzYgMTEuNzQgMTcuOTg3NkMxMy4yNCAxOC4xMzc2IDE0LjUgMTYuOTY3NiAxNC41IDE1LjQ5NzZWNS4xNjc1OEMxNC41IDMuMDc3NTggMTIuOTcgMS4yMTc1OCAxMC44OSAxLjAxNzU4QzguNTEgMC43ODc1ODMgNi41IDIuNjU3NTggNi41IDQuOTk3NThWMTcuMjY3NkM2LjUgMjAuMTM3NiA4LjYgMjIuNzA3NiAxMS40NiAyMi45Nzc2QzE0Ljc1IDIzLjI3NzYgMTcuNSAyMC43MTc2IDE3LjUgMTcuNDk3NlY2Ljc0NzU4QzE3LjUgNi4zMzc1OCAxNy4xNiA1Ljk5NzU4IDE2Ljc1IDUuOTk3NThDMTYuMzQgNS45OTc1OCAxNiA2LjMzNzU4IDE2IDYuNzQ3NThaIiBmaWxsPSIjQjBCNEI3Ii8+CjwvZz4KPC9zdmc+Cg=='); + --str-video__icon--person-off: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMiIGhlaWdodD0iMjMiIHZpZXdCb3g9IjAgMCAyMyAyMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjQ4NDkgMTYuMDcwNUwxNi4zOTU4IDEyLjk3MjFDMTYuOTgyNCAxMy4xNzM4IDE3LjUyMzMgMTMuNDEyMSAxOC4wMTgzIDEzLjY2ODhDMTguOTA3NCAxNC4xMzYzIDE5LjQ2NjYgMTUuMDYyMSAxOS40ODQ5IDE2LjA3MDVaTTIwLjU3NTggMTkuNzU1NUwxOS4yODMzIDIxLjA0OEwxNi44OTA4IDE4LjY2NDZINC44MTgyOFYxNi4xMTYzQzQuODE4MjggMTUuMDg5NiA1LjM3NzQ1IDE0LjE0NTUgNi4yOTQxMSAxMy42NzhDNy40NzY2MSAxMy4wNzMgOC45MjQ5NSAxMi41NTk2IDEwLjU3NDkgMTIuMzQ4OEwyLjQyNTc4IDQuMTk5NjNMMy43MTgyOCAyLjkwNzE0TDIwLjU3NTggMTkuNzU1NVpNMTUuMDU3NCAxNi44MzEzTDEyLjMwNzQgMTQuMDgxM0MxMi4yNTI0IDE0LjA4MTMgMTIuMjA2NiAxNC4wODEzIDEyLjE1MTYgMTQuMDgxM0M5Ljk3OTExIDE0LjA4MTMgOC4yMTkxMSAxNC43NTA1IDcuMTI4MjggMTUuMzA5NkM2LjgzNDk1IDE1LjQ1NjMgNi42NTE2MSAxNS43NjggNi42NTE2MSAxNi4xMTYzVjE2LjgzMTNIMTUuMDU3NFpNMTIuMTUxNiA1LjgzMTNDMTMuMTU5OSA1LjgzMTMgMTMuOTg0OSA2LjY1NjMgMTMuOTg0OSA3LjY2NDY0QzEzLjk4NDkgOC40NTI5NyAxMy40ODk5IDkuMTIyMTQgMTIuNzkzMyA5LjM3ODhMMTQuMTQ5OSAxMC43MzU1QzE1LjE1ODMgMTAuMDg0NiAxNS44MTgzIDguOTQ3OTcgMTUuODE4MyA3LjY2NDY0QzE1LjgxODMgNS42Mzg4IDE0LjE3NzQgMy45OTc5NyAxMi4xNTE2IDMuOTk3OTdDMTAuODY4MyAzLjk5Nzk3IDkuNzMxNjEgNC42NTc5NyA5LjA4MDc4IDUuNjY2M0wxMC40Mzc0IDcuMDIyOTdDMTAuNjk0MSA2LjMyNjMgMTEuMzYzMyA1LjgzMTMgMTIuMTUxNiA1LjgzMTNaIiBmaWxsPSIjRkFGQUZBIi8+Cjwvc3ZnPgo='); + --str-video__icon--pin: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xOSAxMi44N0MxOSAxMi40IDE4LjY2IDEyLjAyIDE4LjIgMTEuODlDMTYuOTMgMTEuNTQgMTYgMTAuMzggMTYgOVY0SDE3QzE3LjU1IDQgMTggMy41NSAxOCAzQzE4IDIuNDUgMTcuNTUgMiAxNyAySDdDNi40NSAyIDYgMi40NSA2IDNDNiAzLjU1IDYuNDUgNCA3IDRIOFY5QzggMTAuMzggNy4wNyAxMS41NCA1LjggMTEuODlDNS4zNCAxMi4wMiA1IDEyLjQgNSAxMi44N1YxM0M1IDEzLjU1IDUuNDUgMTQgNiAxNEgxMC45OEwxMSAyMUMxMSAyMS41NSAxMS40NSAyMiAxMiAyMkMxMi41NSAyMiAxMyAyMS41NSAxMyAyMUwxMi45OCAxNEgxOEMxOC41NSAxNCAxOSAxMy41NSAxOSAxM1YxMi44N1oiIGZpbGw9IiNFM0U0RTUiLz4KPC9zdmc+Cg=='); + --str-video__icon--provider-google: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMCAyMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzk1MV81NjAwMykiPgo8cGF0aCBkPSJNMTguOCAxMS4xODU5QzE4LjggMTAuNTM1OSAxOC43NDE3IDkuOTEwODggMTguNjMzMyA5LjMxMDg4SDEwVjEyLjg2MDlIMTQuOTMzM0MxNC43MTY3IDE0LjAwMjUgMTQuMDY2NyAxNC45NjkyIDEzLjA5MTcgMTUuNjE5MlYxNy45Mjc1SDE2LjA2NjdDMTcuOCAxNi4zMjc1IDE4LjggMTMuOTc3NSAxOC44IDExLjE4NTlaIiBmaWxsPSIjNDI4NUY0Ii8+CjxwYXRoIGQ9Ik05Ljk5OTc0IDIwLjE0NDJDMTIuNDc0NyAyMC4xNDQyIDE0LjU0OTcgMTkuMzI3NSAxNi4wNjY0IDE3LjkyNzVMMTMuMDkxNCAxNS42MTkyQzEyLjI3NDcgMTYuMTY5MiAxMS4yMzMxIDE2LjUwMjUgOS45OTk3NCAxNi41MDI1QzcuNjE2NDEgMTYuNTAyNSA1LjU5MTQxIDE0Ljg5NDIgNC44NjY0MSAxMi43Mjc1SDEuODE2NDFWMTUuMDk0MkMzLjMyNDc0IDE4LjA4NTkgNi40MTY0MSAyMC4xNDQyIDkuOTk5NzQgMjAuMTQ0MloiIGZpbGw9IiMzNEE4NTMiLz4KPHBhdGggZD0iTTQuODY1MzYgMTIuNzE5MkM0LjY4MjAzIDEyLjE2OTIgNC41NzM3IDExLjU4NTkgNC41NzM3IDEwLjk3NzVDNC41NzM3IDEwLjM2OTIgNC42ODIwMyA5Ljc4NTg3IDQuODY1MzYgOS4yMzU4N1Y2Ljg2OTJIMS44MTUzNkMxLjE5MDM2IDguMTAyNTQgMC44MzIwMzEgOS40OTQyIDAuODMyMDMxIDEwLjk3NzVDMC44MzIwMzEgMTIuNDYwOSAxLjE5MDM2IDEzLjg1MjUgMS44MTUzNiAxNS4wODU5TDQuMTkwMzYgMTMuMjM1OUw0Ljg2NTM2IDEyLjcxOTJaIiBmaWxsPSIjRkJCQzA1Ii8+CjxwYXRoIGQ9Ik05Ljk5OTc0IDUuNDYwODhDMTEuMzQ5NyA1LjQ2MDg4IDEyLjU0OTcgNS45Mjc1NSAxMy41MDgxIDYuODI3NTVMMTYuMTMzMSA0LjIwMjU1QzE0LjU0MTQgMi43MTkyMiAxMi40NzQ3IDEuODEwODggOS45OTk3NCAxLjgxMDg4QzYuNDE2NDEgMS44MTA4OCAzLjMyNDc0IDMuODY5MjIgMS44MTY0MSA2Ljg2OTIyTDQuODY2NDEgOS4yMzU4OEM1LjU5MTQxIDcuMDY5MjIgNy42MTY0MSA1LjQ2MDg4IDkuOTk5NzQgNS40NjA4OFoiIGZpbGw9IiNFQTQzMzUiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF85NTFfNTYwMDMiPgo8cmVjdCB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuOTc3NTM5KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo='); --str-video__icon--no-audio: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMTUiIHZpZXdCb3g9IjAgMCAyMCAxNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik05LjMzNjI5IDAuNzcyNzA5TDkuNzQ5MjQgMC40NDI2MTNDOS45MzYxNCAwLjI5MjkyOSAxMC4yMTE5IDAuMzE5NTk2IDEwLjM2NTEgMC41MDIxNzZDMTAuNDI5MyAwLjU3ODY3NyAxMC40NjQ0IDAuNjc0NTYzIDEwLjQ2NDMgMC43NzM0ODdWMS4zMzU0NlYxLjMzNzE1VjEuODY0NDdWMS45MjUyM1YxMy4yMDcyVjEzLjI2ODFWMTMuNzk1NVYxMy43OTcxVjE0LjM1OUMxMC40NjQ0IDE0LjU5NTEgMTAuMjY4NiAxNC43ODY2IDEwLjAyNjkgMTQuNzg2OEM5LjkyNTY4IDE0Ljc4NjggOS44Mjc1MyAxNC43NTI2IDkuNzQ5MjIgMTQuNjg5OUw5LjMzNjI2IDE0LjM1OTdMOS4zMzUzOSAxNC4zNTlMOC45MDg2MSAxNC4wMTc4TDguODU5NzMgMTMuOTc4N0w1LjExNjcyIDEwLjk4NjFMNS4xMTU4NSAxMC45ODYxSDEuNzEyMTNDMS4yMjg3NiAxMC45ODYxIDAuODM2OTE0IDEwLjYwMzMgMC44MzY5MTQgMTAuMTMxMlY1LjAwMTMzQzAuODM2OTE0IDQuNTI5MTQgMS4yMjg3NiA0LjE0NjM1IDEuNzEyMTMgNC4xNDYzNUg1LjExNTg1TDguODU5NzMgMS4xNTM2NUw4LjkwOTY0IDEuMTEzNzVMOS4zMzUzMiAwLjc3MzQ4N0w5LjMzNjI5IDAuNzcyNzA5Wk02LjExNzcyIDUuMzk5N0w4Ljg1OTczIDMuMjA3ODVWMTEuOTI0M0w2LjExODcxIDkuNzMyODhMNS42Nzg2NSA5LjM4MTA1TDUuMTE1ODUgOS4zODE1N0g1LjExNTIzTDUuMTE0MzYgOS4zODE1N0wyLjQ0MTQ4IDkuMzgxNTdWNS43NTA5Mkg1LjExNTg1SDUuNjc4MzRMNi4xMTc3MiA1LjM5OTdaTTE5LjUyNDQgOS44NjQ0NkwxNy4yMjYyIDcuNTY2MjRMMTkuNTI0NCA1LjI2ODAxQzE5LjgzNzcgNC45NTQ3IDE5LjgzMiA0LjQ0MTA4IDE5LjUxODcgNC4xMjc3N0MxOS4yMDU0IDMuODE0NDYgMTguNjkxOCAzLjgwODgyIDE4LjM3ODUgNC4xMjIxM0wxNi4wODAzIDYuNDIwMzVMMTMuNzgyIDQuMTIyMTNDMTMuNDY4NyAzLjgwODgyIDEyLjk1NTEgMy44MTQ0NiAxMi42NDE4IDQuMTI3NzdDMTIuMzI4NSA0LjQ0MTA4IDEyLjMyMjggNC45NTQ3IDEyLjYzNjIgNS4yNjgwMUwxNC45MzQ0IDcuNTY2MjRMMTIuNjM2MiA5Ljg2NDQ2QzEyLjMyMjggMTAuMTc3OCAxMi4zMjg1IDEwLjY5MTQgMTIuNjQxOCAxMS4wMDQ3QzEyLjk1NTEgMTEuMzE4IDEzLjQ2ODcgMTEuMzIzNyAxMy43ODIgMTEuMDEwM0wxNi4wODAzIDguNzEyMTJMMTguMzc4NSAxMS4wMTAzQzE4LjY5MTggMTEuMzIzNyAxOS4yMDU0IDExLjMxOCAxOS41MTg3IDExLjAwNDdDMTkuODMyIDEwLjY5MTQgMTkuODM3NyAxMC4xNzc4IDE5LjUyNDQgOS44NjQ0NloiIGZpbGw9IiM3Mjc2N0UiLz4KPC9zdmc+Cg=='); --str-video__icon--camera-off-outline: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTciIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNyAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTYuODUxMzggNS41MDk5OUw1LjI0NjgyIDMuOTA1NDNMMS45MTczNSAwLjU4Mzk4NEwwLjc4NjEzMyAxLjcxNTJMMi45NzYzNiAzLjkwNTQzSDIuMzkwN0MxLjk0OTQ0IDMuOTA1NDMgMS41ODg0MSA0LjI2NjQ2IDEuNTg4NDEgNC43MDc3MVYxMi43MzA1QzEuNTg4NDEgMTMuMTcxOCAxLjk0OTQ0IDEzLjUzMjggMi4zOTA3IDEzLjUzMjhIMTIuMDE4MUMxMi4xODY2IDEzLjUzMjggMTIuMzMxIDEzLjQ2ODYgMTIuNDU5MyAxMy4zODg0TDE1LjAxMDYgMTUuOTM5N0wxNi4xNDE4IDE0LjgwODRMOS4wMzM1OSA3LjcwMDIyTDYuODUxMzggNS41MDk5OVpNMy4xOTI5OCAxMS45MjgyVjUuNTA5OTlINC41ODA5MkwxMC45OTkyIDExLjkyODJIMy4xOTI5OFpNMTEuMjE1OCA1LjUwOTk5VjcuNjAzOTVMMTYuMDI5NSAxMi40MTc2VjQuMzA2NTdMMTIuODIwNCA3LjUxNTdWNC43MDc3MUMxMi44MjA0IDQuMjY2NDYgMTIuNDU5MyAzLjkwNTQzIDEyLjAxODEgMy45MDU0M0g3LjUxNzI4TDkuMTIxODQgNS41MDk5OUgxMS4yMTU4WiIgZmlsbD0iIzcyNzY3RSIvPgo8L3N2Zz4K'); --str-video__icon--not-allowed: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTciIGhlaWdodD0iMTciIHZpZXdCb3g9IjAgMCAxNyAxNyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTguNDY0MjIgMC45MzU1NDdDNC4wMzU2MyAwLjkzNTU0NyAwLjQ0MTQwNiA0LjUyOTc3IDAuNDQxNDA2IDguOTU4MzZDMC40NDE0MDYgMTMuMzg3IDQuMDM1NjMgMTYuOTgxMiA4LjQ2NDIyIDE2Ljk4MTJDMTIuODkyOCAxNi45ODEyIDE2LjQ4NyAxMy4zODcgMTYuNDg3IDguOTU4MzZDMTYuNDg3IDQuNTI5NzcgMTIuODkyOCAwLjkzNTU0NyA4LjQ2NDIyIDAuOTM1NTQ3Wk0yLjA0NTk3IDguOTU4MzZDMi4wNDU5NyA1LjQxMjI4IDQuOTE4MTQgMi41NDAxMSA4LjQ2NDIyIDIuNTQwMTFDOS45NDg0NCAyLjU0MDExIDExLjMxMjMgMy4wNDU1NSAxMi4zOTU0IDMuODk1OTdMMy40MDE4MyAxMi44ODk1QzIuNTUxNDEgMTEuODA2NSAyLjA0NTk3IDEwLjQ0MjYgMi4wNDU5NyA4Ljk1ODM2Wk04LjQ2NDIyIDE1LjM3NjZDNi45OCAxNS4zNzY2IDUuNjE2MTIgMTQuODcxMiA0LjUzMzA0IDE0LjAyMDhMMTMuNTI2NiA1LjAyNzE4QzE0LjM3NyA2LjExMDI2IDE0Ljg4MjUgNy40NzQxNCAxNC44ODI1IDguOTU4MzZDMTQuODgyNSAxMi41MDQ0IDEyLjAxMDMgMTUuMzc2NiA4LjQ2NDIyIDE1LjM3NjZaIiBmaWxsPSIjNzI3NjdFIi8+Cjwvc3ZnPgo='); diff --git a/sample-apps/react/audio-rooms/package.json b/sample-apps/react/audio-rooms/package.json index f54db55115..004f52f4d3 100644 --- a/sample-apps/react/audio-rooms/package.json +++ b/sample-apps/react/audio-rooms/package.json @@ -20,7 +20,6 @@ "@types/react-dom": "^18.2.13", "@vitejs/plugin-react": "^4.1.0", "typescript": "^5.2.2", - "vercel": "^32.4.1", "vite": "^4.4.11" } } diff --git a/sample-apps/react/cookbook-participant-list/src/App.tsx b/sample-apps/react/cookbook-participant-list/src/App.tsx index 9f0f5ddb7a..a8dd57aef5 100644 --- a/sample-apps/react/cookbook-participant-list/src/App.tsx +++ b/sample-apps/react/cookbook-participant-list/src/App.tsx @@ -1,5 +1,5 @@ -import './App.scss'; -import { createTheme, ThemeProvider } from '@mui/material'; +import createTheme from '@mui/material/styles/createTheme'; +import ThemeProvider from '@mui/material/styles/ThemeProvider'; import { CallSetup } from './CallSetup'; import { useEffect, useState } from 'react'; import { @@ -49,7 +49,7 @@ const App = () => { {!callId && } - {callId && ( + {call && ( {call && ( diff --git a/sample-apps/react/cookbook-participant-list/src/CallSetup.tsx b/sample-apps/react/cookbook-participant-list/src/CallSetup.tsx index 1633d8a24f..f27b381bc3 100644 --- a/sample-apps/react/cookbook-participant-list/src/CallSetup.tsx +++ b/sample-apps/react/cookbook-participant-list/src/CallSetup.tsx @@ -1,4 +1,7 @@ -import { Button, Input, Stack, Typography } from '@mui/material'; +import Button from '@mui/material/Button'; +import Input from '@mui/material/Input'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; import { useState } from 'react'; export type CallSetupProps = { diff --git a/sample-apps/react/cookbook-participant-list/src/SpeakerView.scss b/sample-apps/react/cookbook-participant-list/src/SpeakerView.scss index 4e9bef9a5a..d2130e3e1c 100644 --- a/sample-apps/react/cookbook-participant-list/src/SpeakerView.scss +++ b/sample-apps/react/cookbook-participant-list/src/SpeakerView.scss @@ -14,6 +14,7 @@ .str-video__participant-view { height: 100%; width: auto; + aspect-ratio: auto; } .str-video__participant-view--no-video { @@ -38,6 +39,10 @@ width: 240px; min-width: 240px; + .str-video__participant-view { + aspect-ratio: auto; + } + &:first-child { margin-left: auto; } diff --git a/sample-apps/react/cookbook-participant-list/src/main.tsx b/sample-apps/react/cookbook-participant-list/src/main.tsx index 0dc1bed0ea..09de8ad7e6 100644 --- a/sample-apps/react/cookbook-participant-list/src/main.tsx +++ b/sample-apps/react/cookbook-participant-list/src/main.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import '@stream-io/video-styling/dist/css/styles.css'; import './index.css'; +import './App.scss'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( , diff --git a/sample-apps/react/egress-composite/README.md b/sample-apps/react/egress-composite/README.md index ccad81cada..c7a67d8598 100644 --- a/sample-apps/react/egress-composite/README.md +++ b/sample-apps/react/egress-composite/README.md @@ -23,3 +23,10 @@ As such, this application accepts certain configuration parameters to be provide - run `yarn start` - open browser - uncomment and tweak the setup script at the bottom of the [`main.tsx`](./src/main.tsx) file to your likings + +## To update the Playwright test baseline screenshots + +- in the root of the repo, edit `docker-compose.yml` and provide the API Key and User Token +- run `docker compose up generate-screenshots` +- check the generated screenshots in `./tests/__screenshots__/` +- push the changes to the repo diff --git a/sample-apps/react/egress-composite/package.json b/sample-apps/react/egress-composite/package.json index 55f6feff91..3ca460341e 100644 --- a/sample-apps/react/egress-composite/package.json +++ b/sample-apps/react/egress-composite/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@emotion/css": "^11.11.2", - "@sentry/react": "^7.70.0", + "@sentry/react": "^7.91.0", "@sentry/vite-plugin": "^2.7.1", "@stream-io/video-react-sdk": "workspace:^", "clsx": "^2.0.0", @@ -25,10 +25,8 @@ "@types/react": "^18.2.28", "@types/react-dom": "^18.2.13", "@vitejs/plugin-react": "^4.1.0", - "axios": "^1.5.1", - "nanoid": "^5.0.2", + "nanoid": "^5.0.4", "typescript": "^5.2.2", - "vercel": "^32.4.1", "vite": "^4.4.11" } } diff --git a/sample-apps/react/egress-composite/src/hooks/options/useParticipantLabelStyles.ts b/sample-apps/react/egress-composite/src/hooks/options/useParticipantLabelStyles.ts index 19d02af6a7..b52db0c2ca 100644 --- a/sample-apps/react/egress-composite/src/hooks/options/useParticipantLabelStyles.ts +++ b/sample-apps/react/egress-composite/src/hooks/options/useParticipantLabelStyles.ts @@ -12,7 +12,7 @@ export const useParticipantLabelStyles = () => { 'participant_label.display': participantLabelDisplay = true, 'participant_label.text_color': participantLabelTextColor, 'participant_label.background_color': participantLabelBackgroundColor, - 'participant_label.border_width': participantLabelBorderWidth = '0px', + 'participant_label.border_width': participantLabelBorderWidth = '0', 'participant_label.border_radius': participantLabelBorderRadius, 'participant_label.border_color': participantLabelBorderColor = 'rgba(0,0,0,0)', @@ -20,9 +20,8 @@ export const useParticipantLabelStyles = () => { participantLabelHorizontalPosition = 'left', 'participant_label.vertical_position': participantLabelVerticalPosition = 'bottom', - 'participant_label.margin_inline': - participantLabelMarginInline = '.875rem', - 'participant_label.margin_block': participantLabelMarginBlock = '.875rem', + 'participant_label.margin_inline': participantLabelMarginInline = '0', + 'participant_label.margin_block': participantLabelMarginBlock = '0', }, } = useConfigurationContext(); @@ -70,10 +69,6 @@ export const useParticipantLabelStyles = () => { transition: unset; opacity: unset; - left: unset; - bottom: unset; - right: unset; - top: unset; } } `, diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/genericLayoutStyles.spec.ts/Generic-Layout-Styles-Should-have-proper-background-with-applied-styling-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/genericLayoutStyles.spec.ts/Generic-Layout-Styles-Should-have-proper-background-with-applied-styling-1.png index 3d66226264..7c668beef4 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/genericLayoutStyles.spec.ts/Generic-Layout-Styles-Should-have-proper-background-with-applied-styling-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/genericLayoutStyles.spec.ts/Generic-Layout-Styles-Should-have-proper-background-with-applied-styling-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---default-undefined---fall-back-to-spotlight-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---default-undefined---fall-back-to-spotlight-1.png index 77bccb1e38..11f1820010 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---default-undefined---fall-back-to-spotlight-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---default-undefined---fall-back-to-spotlight-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---single-participant-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---single-participant-1.png index 298eebc1c8..b9dbec7762 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---single-participant-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---single-participant-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---spotlight-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---spotlight-1.png index 77bccb1e38..11f1820010 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---spotlight-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---spotlight-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---unknown-fall-back-to-spotlight-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---unknown-fall-back-to-spotlight-1.png index 77bccb1e38..11f1820010 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---unknown-fall-back-to-spotlight-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/layouts.spec.ts/Layouts-Should-render-screenshare-layout---unknown-fall-back-to-spotlight-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-custom-options-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-custom-options-1.png index d8a2b45090..8a101215b2 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-custom-options-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-custom-options-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-defaults-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-defaults-1.png index e929268e4d..c31a04624a 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-defaults-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/logoAndTitle.spec.ts/Logo-and-Title-Should-render-logo-and-title-with-defaults-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/participantLabelStyles.spec.ts/Participant-Label-Styles-Should-render-participant-labels-with-custom-options-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/participantLabelStyles.spec.ts/Participant-Label-Styles-Should-render-participant-labels-with-custom-options-1.png index 6998fdc50c..08ee260baf 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/participantLabelStyles.spec.ts/Participant-Label-Styles-Should-render-participant-labels-with-custom-options-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/participantLabelStyles.spec.ts/Participant-Label-Styles-Should-render-participant-labels-with-custom-options-1.png differ diff --git a/sample-apps/react/egress-composite/tests/__screenshots__/participantStyles.spec.ts/Participant-Styles-Should-render-participant-labels-with-custom-options-1.png b/sample-apps/react/egress-composite/tests/__screenshots__/participantStyles.spec.ts/Participant-Styles-Should-render-participant-labels-with-custom-options-1.png index b620b1710e..d0e717a080 100644 Binary files a/sample-apps/react/egress-composite/tests/__screenshots__/participantStyles.spec.ts/Participant-Styles-Should-render-participant-labels-with-custom-options-1.png and b/sample-apps/react/egress-composite/tests/__screenshots__/participantStyles.spec.ts/Participant-Styles-Should-render-participant-labels-with-custom-options-1.png differ diff --git a/sample-apps/react/egress-composite/tests/baseTests.ts b/sample-apps/react/egress-composite/tests/baseTests.ts index e5207db2ca..484e593f24 100644 --- a/sample-apps/react/egress-composite/tests/baseTests.ts +++ b/sample-apps/react/egress-composite/tests/baseTests.ts @@ -1,6 +1,5 @@ import { test as base } from '@playwright/test'; import { customAlphabet } from 'nanoid'; -import axios from 'axios'; const nanoid = customAlphabet('1234567890abcdefghijklmnop', 10); @@ -14,30 +13,3 @@ export const testWithCallId = base.extend<{ callId: string }>({ await use(callId); }, }); - -// TODO: find better name -export const testWithBuddy = base.extend<{ callId: string }>({ - callId: async ({ page }, use) => { - const callId = nanoid(); - - // TODO: have proper abstractions with typing like StreamVideoBuddy.join(...) -> id and StreamVideoBuddy.teardown(id) - await axios.post('http://localhost:4567/stream-video-buddy?async=true', { - duration: 60, - 'call-id': callId, - 'user-count': 4, - }); - - // TODO: have proper ?asyncJoin=true - // which waits only for successfull call creation - // but asyncs user joins - await new Promise((resolve) => setTimeout(resolve, 2000)); - - await page.goto('/', { waitUntil: 'domcontentloaded' }); - - // run tests - await use(callId); - - // teardown - // TODO: await StreamVideoBuddy.teardown()... - }, -}); diff --git a/sample-apps/react/livestream-app/package.json b/sample-apps/react/livestream-app/package.json index b9a57feba4..c429843f38 100644 --- a/sample-apps/react/livestream-app/package.json +++ b/sample-apps/react/livestream-app/package.json @@ -26,7 +26,6 @@ "@types/react-dom": "^18.2.13", "@vitejs/plugin-react": "^4.1.0", "typescript": "^5.2.2", - "vercel": "^32.4.1", "vite": "^4.4.11" } } diff --git a/sample-apps/react/messenger-clone/package.json b/sample-apps/react/messenger-clone/package.json index ab4e085c49..4f964f3c0b 100644 --- a/sample-apps/react/messenger-clone/package.json +++ b/sample-apps/react/messenger-clone/package.json @@ -17,13 +17,13 @@ "clsx": "^2.0.0", "dayjs": "^1.11.6", "immer": "^9.0.16", - "nanoid": "^5.0.2", + "nanoid": "^5.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.17.0", - "sass": "^1.69.3", - "stream-chat": "^8.13.0", - "stream-chat-react": "^10.14.0" + "sass": "^1.69.5", + "stream-chat": "^8.14.4", + "stream-chat-react": "^11.2.1" }, "devDependencies": { "@types/react": "^18.2.28", @@ -31,7 +31,6 @@ "@vitejs/plugin-react": "^4.1.0", "dotenv": "^16.3.1", "typescript": "^5.2.2", - "vercel": "^32.4.1", "vite": "^4.4.11" } } diff --git a/sample-apps/react/react-dogfood/.env.example b/sample-apps/react/react-dogfood/.env.example index 7b3cf80090..bf6d1ca2bb 100644 --- a/sample-apps/react/react-dogfood/.env.example +++ b/sample-apps/react/react-dogfood/.env.example @@ -23,6 +23,19 @@ NEXT_PUBLIC_DISABLE_CHAT=false ### Lets you override the app environment to use NEXT_PUBLIC_APP_ENVIRONMENT=pronto +### Lets you configure the mapbox token - if not set, the map will not be displayed +NEXT_PUBLIC_MAPBOX_GL_TOKEN= + +### Lets you configure the google analytics and tag manager ids +NEXT_PUBLIC_GA_ID= +NEXT_PUBLIC_GTM_ID= + +### Lets you configure the base path for the app, defaults to / +NEXT_PUBLIC_BASE_PATH= + +### Lets you configure the Sentry DSN +NEXT_PUBLIC_SENTRY_DSN= + ### Lets you configure the token generator SAMPLE_APP_CALL_CONFIG='{ "pronto": { diff --git a/sample-apps/react/react-dogfood/components/ActiveCall.tsx b/sample-apps/react/react-dogfood/components/ActiveCall.tsx new file mode 100644 index 0000000000..c4de160ff5 --- /dev/null +++ b/sample-apps/react/react-dogfood/components/ActiveCall.tsx @@ -0,0 +1,278 @@ +import clsx from 'clsx'; +import { useEffect, useState } from 'react'; +import { + Call, + CallingState, + CallParticipantsList, + CancelCallConfirmButton, + CompositeButton, + Icon, + PermissionRequests, + ReactionsButton, + RecordCallConfirmationButton, + RecordingInProgressNotification, + ScreenShareButton, + SpeakingWhileMutedNotification, + useCallStateHooks, +} from '@stream-io/video-react-sdk'; +import { StreamChat } from 'stream-chat'; + +import { ActiveCallHeader } from './ActiveCallHeader'; +import { Stage } from './Stage'; +import { InvitePanel, InvitePopup } from './InvitePanel/InvitePanel'; +import { ChatWrapper } from './ChatWrapper'; +import { ChatUI } from './ChatUI'; +import { CallStatsSidebar, ToggleStatsButton } from './CallStatsWrapper'; +import { ToggleSettingsTabModal } from './Settings/SettingsTabModal'; +import { ToggleFeedbackButton } from './ToggleFeedbackButton'; +import { ToggleDeveloperButton } from './ToggleDeveloperButton'; +import { ToggleMoreOptionsListButton } from './ToggleMoreOptionsListButton'; +import { ToggleLayoutButton } from './ToggleLayoutButton'; +import { ToggleParticipantListButton } from './ToggleParticipantListButton'; +import { ToggleDualCameraButton } from './ToggleDualCameraButton'; +import { ToggleDualMicButton } from './ToggleDualMicButton'; +import { NewMessageNotification } from './NewMessageNotification'; +import { UnreadCountBadge } from './UnreadCountBadge'; +import { TourPanel } from './TourPanel'; + +import { useBreakpoint, useLayoutSwitcher, useWatchChannel } from '../hooks'; +import { + useIsDemoEnvironment, + useIsProntoEnvironment, +} from '../context/AppEnvironmentContext'; + +import { StepNames, useTourContext } from '../context/TourContext'; + +export type ActiveCallProps = { + chatClient?: StreamChat | null; + activeCall: Call; + onLeave: () => void; + onJoin: ({ fastJoin }: { fastJoin: boolean }) => void; +}; + +type SidebarContent = 'participants' | 'chat' | 'stats' | null; + +export const ActiveCall = (props: ActiveCallProps) => { + const { chatClient, activeCall, onLeave, onJoin } = props; + const { useParticipantCount } = useCallStateHooks(); + const participantCount = useParticipantCount(); + const { + current: currentTourStep, + active: isTourActive, + next: nextTourStep, + } = useTourContext(); + + const { layout, setLayout } = useLayoutSwitcher(); + const breakpoint = useBreakpoint(); + useEffect(() => { + if ( + (layout === 'SpeakerLeft' || layout === 'SpeakerRight') && + (breakpoint === 'xs' || breakpoint === 'sm') + ) { + setLayout('SpeakerBottom'); + } + }, [breakpoint, layout, setLayout]); + + const isDemoEnvironment = useIsDemoEnvironment(); + const isPronto = useIsProntoEnvironment(); + + const [showInvitePopup, setShowInvitePopup] = useState( + isDemoEnvironment && !isTourActive, + ); + const [sidebarContent, setSidebarContent] = useState(null); + const showSidebar = sidebarContent != null; + const showParticipants = sidebarContent === 'participants'; + const showChat = sidebarContent === 'chat'; + const showStats = sidebarContent === 'stats'; + + // FIXME: could be replaced with "notification.message_new" but users would have to be at least members + // possible fix with "allow to join" permissions in place (expensive?) + const channelWatched = useWatchChannel({ + chatClient, + channelId: activeCall?.id, + }); + + useEffect(() => { + // helps with Fast-Refresh + if (activeCall?.state.callingState === CallingState.IDLE) { + onJoin({ fastJoin: true }); + } + }, [activeCall, onJoin]); + + useEffect(() => { + if (currentTourStep === StepNames.Chat) { + setSidebarContent('chat'); + } else { + setSidebarContent(null); + } + }, [currentTourStep]); + + useEffect(() => { + if (isDemoEnvironment && !isTourActive) { + setShowInvitePopup(true); + } + }, [isDemoEnvironment, isTourActive]); + + return ( +
    + {isDemoEnvironment && } +
    + + + +
    + + {showInvitePopup && participantCount === 1 && ( + setShowInvitePopup(false)} + /> + )} + +
    + {showSidebar && ( +
    + {showParticipants && ( +
    + setSidebarContent(null)} + /> + +
    + )} + + {showChat && ( + +
    + { + if ( + isTourActive && + currentTourStep === StepNames.Chat + ) { + nextTourStep(); + } + setSidebarContent(null); + }} + channelId={activeCall.id} + /> +
    +
    + )} + + {showStats && } +
    + )} +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    + +
    + {isPronto && ( +
    + +
    + )} +
    + +
    +
    +
    + + +
    + +
    +
    + +
    + + + +
    + +
    +
    +
    +
    + +
    +
    + setSidebarContent(showStats ? null : 'stats')} + /> +
    + + { + setSidebarContent(showParticipants ? null : 'participants'); + }} + /> + +
    + { + if (isTourActive && currentTourStep === StepNames.Chat) { + nextTourStep(); + } + setSidebarContent(showChat ? null : 'chat'); + }} + > + + + {!showChat && ( + + )} +
    +
    +
    +
    +
    +
    + ); +}; diff --git a/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx b/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx index a81c9eb8f7..19443ab985 100644 --- a/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx +++ b/sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx @@ -1,76 +1,127 @@ -import MoreVertIcon from '@mui/icons-material/MoreVert'; +import { useEffect, useMemo, useState } from 'react'; import { CallingState, - CopyToClipboardButtonWithPopup, - DeviceSettings, - IconButton, + CancelCallConfirmButton, + Icon, LoadingIndicator, Notification, useCallStateHooks, + useI18n, } from '@stream-io/video-react-sdk'; +import clsx from 'clsx'; + import { CallHeaderTitle } from './CallHeaderTitle'; -import { CallRecordings } from './CallRecordings'; -import { USAGE_GUIDE_LINK } from './index'; -import { IconInviteLinkButton } from './InviteLinkButton'; -import { LayoutSelector, LayoutSelectorProps } from './LayoutSelector'; -import { useSettings } from '../context/SettingsContext'; -import { DevMenu } from './DevMenu'; +import { ToggleSettingsTabModal } from './Settings/SettingsTabModal'; +import { ToggleDocumentationButton } from './ToggleDocumentationButton'; + +import { LayoutSelectorProps } from './LayoutSelector'; + +import { useIsDemoEnvironment } from '../context/AppEnvironmentContext'; + +export const LatencyIndicator = () => { + const { useCallStatsReport } = useCallStateHooks(); + const statsReport = useCallStatsReport(); + const latency = statsReport?.publisherStats?.averageRoundTripTimeInMs ?? 0; + + return ( +
    +
    100 && latency < 400, + 'rd__header__latency-indicator--bad': latency && latency > 400, + })} + >
    + {latency} ms +
    + ); +}; + +export const Elapsed = ({ + startedAt, +}: { + className?: string; + startedAt: string | undefined; +}) => { + const [elapsed, setElapsed] = useState(); + const startedAtDate = useMemo( + () => (startedAt ? new Date(startedAt).getTime() : Date.now()), + [startedAt], + ); + useEffect(() => { + const interval = setInterval(() => { + const elapsedSeconds = (Date.now() - startedAtDate) / 1000; + const date = new Date(0); + date.setSeconds(elapsedSeconds); + const format = date.toISOString(); // '1970-01-01T00:00:35.000Z' + const hours = format.substring(11, 13); + const minutes = format.substring(14, 16); + const seconds = format.substring(17, 19); + const time = `${hours !== '00' ? hours + ':' : ''}${minutes}:${seconds}`; + setElapsed(time); + }, 1000); + return () => clearInterval(interval); + }, [startedAtDate]); + + return ( +
    + +
    {elapsed}
    +
    + ); +}; export const ActiveCallHeader = ({ + onLeave, selectedLayout, - onMenuItemClick: setLayout, -}: LayoutSelectorProps) => { - const { setOpen } = useSettings(); - const { useCallCallingState } = useCallStateHooks(); + onMenuItemClick, +}: { onLeave: () => void } & LayoutSelectorProps) => { + const { useCallCallingState, useCallSession } = useCallStateHooks(); const callingState = useCallCallingState(); + const session = useCallSession(); const isOffline = callingState === CallingState.OFFLINE; const isMigrating = callingState === CallingState.MIGRATING; const isJoining = callingState === CallingState.JOINING; const isReconnecting = callingState === CallingState.RECONNECTING; const hasFailedToRecover = callingState === CallingState.RECONNECTING_FAILED; + const { t } = useI18n(); + + const isDemo = useIsDemoEnvironment(); + return ( <> -
    - -
    - - +
    + - { - if (typeof window !== 'undefined') { - window.open(USAGE_GUIDE_LINK, '_blank', 'noopener,noreferrer'); - } + + +
    + +
    + - - - - + /> +
    + +
    + + +
    +
    +
    -
    +
    {(() => { if (isOffline || hasFailedToRecover) { return ( diff --git a/sample-apps/react/react-dogfood/components/CallHeaderTitle.tsx b/sample-apps/react/react-dogfood/components/CallHeaderTitle.tsx index 944af9cb23..f89cabcc60 100644 --- a/sample-apps/react/react-dogfood/components/CallHeaderTitle.tsx +++ b/sample-apps/react/react-dogfood/components/CallHeaderTitle.tsx @@ -21,21 +21,18 @@ export const CallHeaderTitle = ({ title }: CallTitleProps) => { if (!connectedUser) return 'Connecting...'; if (!participants.length) return connectedUser.name; - return ( - 'Call with: ' + - participants - .slice(0, 3) - .map((p) => p.name || p.userId) - .join(', ') - ); + return participants + .slice(0, 3) + .map((p) => p.name || p.userId) + .join(', '); }, [connectedUser, participants]); if (!activeCall) return null; return ( -
    +
    -

    {title || standInTitle}

    +

    {title || standInTitle}

    ); }; diff --git a/sample-apps/react/react-dogfood/components/CallLayout/CallParticipantsScreenView.tsx b/sample-apps/react/react-dogfood/components/CallLayout/CallParticipantsScreenView.tsx index 4c4406456b..4d821eb5ae 100644 --- a/sample-apps/react/react-dogfood/components/CallLayout/CallParticipantsScreenView.tsx +++ b/sample-apps/react/react-dogfood/components/CallLayout/CallParticipantsScreenView.tsx @@ -1,21 +1,21 @@ import { ComponentType, useEffect, useState } from 'react'; import { - Call, DefaultParticipantViewUI, IconButton, ParticipantView, SfuModels, + useCall, useCallStateHooks, useVerticalScrollPosition, Video, } from '@stream-io/video-react-sdk'; export const CallParticipantsScreenView = (props: { - call: Call; ParticipantViewUI?: ComponentType; }) => { - const { call, ParticipantViewUI } = props; + const { ParticipantViewUI } = props; + const call = useCall(); const { useLocalParticipant, useParticipants } = useCallStateHooks(); const localParticipant = useLocalParticipant(); const allParticipants = useParticipants(); @@ -28,7 +28,7 @@ export const CallParticipantsScreenView = (props: { ); useEffect(() => { - if (!scrollWrapper) return; + if (!scrollWrapper || !call) return; const cleanup = call.setViewport(scrollWrapper); return () => cleanup(); }, [scrollWrapper, call]); diff --git a/sample-apps/react/react-dogfood/components/CallRecordings.tsx b/sample-apps/react/react-dogfood/components/CallRecordings.tsx index 9d02837fc8..98e9291039 100644 --- a/sample-apps/react/react-dogfood/components/CallRecordings.tsx +++ b/sample-apps/react/react-dogfood/components/CallRecordings.tsx @@ -1,14 +1,12 @@ -import clsx from 'clsx'; -import { forwardRef, useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { CallRecording, CallRecordingList, - IconButton, - MenuToggle, - ToggleMenuButtonProps, useCall, } from '@stream-io/video-react-sdk'; +const TIMEOUT_INTERVAL = 1200000; + export const CallRecordings = () => { const call = useCall(); const [callRecordings, setCallRecordings] = useState([]); @@ -22,6 +20,14 @@ export const CallRecordings = () => { }); }, [call]); + useEffect(() => { + const timeout = setTimeout(fetchCallRecordings, TIMEOUT_INTERVAL); + + return () => { + clearTimeout(timeout); + }; + }, [fetchCallRecordings]); + useEffect(() => { fetchCallRecordings(); }, [fetchCallRecordings]); @@ -46,27 +52,10 @@ export const CallRecordings = () => { }, [call]); return ( - - - +
    } + callRecordings={callRecordings} + loading={loadingCallRecordings} + /> ); }; - -const ToggleMenuButton = forwardRef( - ({ menuShown }, ref) => { - return ( - - ); - }, -); diff --git a/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingSearchForm.tsx b/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingSearchForm.tsx index f3090aeb0e..25361b5482 100644 --- a/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingSearchForm.tsx +++ b/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingSearchForm.tsx @@ -6,15 +6,10 @@ import { useRouter } from 'next/router'; import { ChangeEventHandler, FormEventHandler, - KeyboardEventHandler, useCallback, useEffect, useState, } from 'react'; -import { Button, Stack } from '@mui/material'; -import clsx from 'clsx'; - -const CALL_TYPES = ['default', 'development', 'audio-room', 'livestream']; type CallRecordingSearchFormProps = { setLoading: (loading: boolean) => void; @@ -30,8 +25,6 @@ export const CallRecordingSearchForm = ({ const videoClient = useStreamVideoClient(); const [enabled, setEnabled] = useState(false); - const [showDropdown, setShowDropdown] = useState(false); - const [highlightedOption, setHighlightedOption] = useState(0); const [callIdInput, setCallIdInput] = useState(null); const [callTypeInput, setCallTypeInput] = useState( null, @@ -86,48 +79,6 @@ export const CallRecordingSearchForm = ({ setResultError(undefined); }, [callTypeInput, callIdInput, setResult, setResultError]); - const handleDropdownNavigation: KeyboardEventHandler = - useCallback( - (e) => { - if (!callTypeInput) return; - if (e.code === 'ArrowDown') { - if (!showDropdown) { - setShowDropdown(true); - return; - } - setHighlightedOption((prev) => { - return ++prev % CALL_TYPES.length; - }); - } - - if (e.code === 'ArrowUp') { - setHighlightedOption((prev) => { - return prev === 0 - ? CALL_TYPES.length - 1 - : --prev % CALL_TYPES.length; - }); - } - - if (e.code === 'Enter') { - callTypeInput.value = CALL_TYPES[highlightedOption]; - setShowDropdown(false); - setEnabled(!!callIdInput?.value); - setResultError(undefined); - } - - if (e.code === 'Escape' && showDropdown) { - setShowDropdown(false); - } - }, - [ - callTypeInput, - callIdInput, - showDropdown, - highlightedOption, - setResultError, - ], - ); - useEffect(() => { const { callCid } = router.query; @@ -137,15 +88,25 @@ export const CallRecordingSearchForm = ({ callTypeInput.value = callType as string; callIdInput.value = callId as string; - queryRecordings(callType, callId); + queryRecordings(callType, callId).catch((err) => { + console.error('Error querying recordings', err); + setResultError(err as Error); + }); } - }, [callIdInput, callTypeInput, router, router.query, queryRecordings]); + }, [ + callIdInput, + callTypeInput, + router, + router.query, + queryRecordings, + setResultError, + ]); const formId = 'recording-search-form'; return ( - +
    - +
    setShowDropdown(true)} - onBlur={() => setTimeout(() => setShowDropdown(false), 100)} - onKeyDown={handleDropdownNavigation} placeholder="Call Type" - tabIndex={1} + defaultValue="default" /> - {showDropdown && ( -
    - {CALL_TYPES.map((callType, i) => ( -
    { - if (!callTypeInput) return; - callTypeInput.value = callType; - }} - > - {callType} -
    - ))} -
    - )}
    - : - - + Search + - +
    ); }; diff --git a/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingsPage.tsx b/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingsPage.tsx index 1689aea982..c2a870a5df 100644 --- a/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingsPage.tsx +++ b/sample-apps/react/react-dogfood/components/CallRecordingsPage/CallRecordingsPage.tsx @@ -6,12 +6,11 @@ import { StreamVideoClient, } from '@stream-io/video-react-sdk'; import { useEffect, useState } from 'react'; -import { Box } from '@mui/material'; import { CallRecordingSearchForm } from './CallRecordingSearchForm'; -import { LobbyHeader } from '../LobbyHeader'; import { ServerSideCredentialsProps } from '../../lib/getServerSideCredentialsProps'; import { useSettings } from '../../context/SettingsContext'; import { customSentryLogger } from '../../helpers/logger'; +import { DefaultAppHeader } from '../DefaultAppHeader'; export const CallRecordingsPage = ({ apiKey, @@ -19,7 +18,7 @@ export const CallRecordingsPage = ({ userToken, }: ServerSideCredentialsProps) => { const { - settings: { language }, + settings: { language, fallbackLanguage }, } = useSettings(); const [recordings, setRecordings] = useState(); const [error, setError] = useState(); @@ -51,28 +50,25 @@ export const CallRecordingsPage = ({ } return ( - - - - + + +
    +
    {loading ? ( - + ) : error?.message ? (
    {error.message}
    ) : ( - +
    {!recordings ? null : recordings.length @@ -83,10 +79,10 @@ export const CallRecordingsPage = ({ /> )) : 'No recordings found for the call'} - +
    )} -
    - +
    +
    ); }; diff --git a/sample-apps/react/react-dogfood/components/CallSettingsButton.tsx b/sample-apps/react/react-dogfood/components/CallSettingsButton.tsx deleted file mode 100644 index 59791a410a..0000000000 --- a/sample-apps/react/react-dogfood/components/CallSettingsButton.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useState } from 'react'; -import { useCall, useCallStateHooks } from '@stream-io/video-react-sdk'; -import { IconButton } from '@mui/material'; -import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest'; -import Menu from '@mui/material/Menu'; -import MenuList from '@mui/material/MenuList'; -import MenuItem from '@mui/material/MenuItem'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import PanoramaIcon from '@mui/icons-material/Panorama'; -import ListItemText from '@mui/material/ListItemText'; - -export const CallSettingsButton = () => { - const [anchorEl, setAnchorEl] = useState(null); - const open = !!anchorEl; - return ( -
    - { - setAnchorEl(e.currentTarget); - }} - > - - - setAnchorEl(null)} - MenuListProps={{ - 'aria-labelledby': 'call-settings-button', - }} - > - - - - -
    - ); -}; - -const ToggleCallThumbnails = () => { - const call = useCall(); - const { useCallSettings } = useCallStateHooks(); - const settings = useCallSettings(); - const thumbnailsEnabled = !!settings?.thumbnails.enabled; - return ( - { - if (!call) return; - call - .update({ - settings_override: { thumbnails: { enabled: !thumbnailsEnabled } }, - }) - .catch((err) => { - console.error(`Failed to update call thumbnail settings`, err); - }); - }} - > - - - - - {thumbnailsEnabled ? 'Disable Thumbnails' : 'Enable Thumbnails'} - - - ); -}; diff --git a/sample-apps/react/react-dogfood/components/CallStatsWrapper.tsx b/sample-apps/react/react-dogfood/components/CallStatsWrapper.tsx new file mode 100644 index 0000000000..442f458b4e --- /dev/null +++ b/sample-apps/react/react-dogfood/components/CallStatsWrapper.tsx @@ -0,0 +1,26 @@ +import { CallStats, CompositeButton, Icon } from '@stream-io/video-react-sdk'; + +export const ToggleStatsButton = (props: { + active?: boolean; + onClick?: () => void; +}) => { + const { active, onClick } = props; + return ( + + + + ); +}; + +export const CallStatsSidebar = () => { + return ( +
    + +
    + ); +}; diff --git a/sample-apps/react/react-dogfood/components/ChatUI.tsx b/sample-apps/react/react-dogfood/components/ChatUI.tsx index 93ae297802..c57754f8b7 100644 --- a/sample-apps/react/react-dogfood/components/ChatUI.tsx +++ b/sample-apps/react/react-dogfood/components/ChatUI.tsx @@ -1,16 +1,88 @@ -import { useState, useEffect } from 'react'; +import { useEffect, ComponentProps } from 'react'; import { Channel, Window, MessageList, MessageInput, useChatContext, + useChannelStateContext, + MESSAGE_ACTIONS, } from 'stream-chat-react'; -import { TextButton } from '@stream-io/video-react-sdk'; +import type { Message } from 'stream-chat'; + +import { IconButton, Icon } from '@stream-io/video-react-sdk'; import { CHANNEL_TYPE } from '.'; +const ALLOWED_MESSAGE_ACTIONS = [ + MESSAGE_ACTIONS.edit, + MESSAGE_ACTIONS.delete, + MESSAGE_ACTIONS.flag, + MESSAGE_ACTIONS.quote, + MESSAGE_ACTIONS.react, +]; + +export const NoMessages = () => { + const { messages } = useChannelStateContext(); + + if (messages?.length === 0) { + return ( +
    + + + + + + +

    Start chatting!

    +

    + Let’s get this chat started, why not send the first message? +

    +
    + ); + } + return null; +}; + +export type ChatSendButtonProps = { + sendMessage: ( + event: React.BaseSyntheticEvent, + customMessageData?: Partial, + ) => void; +} & ComponentProps<'button'>; + +export const ChatSendButton = ({ + sendMessage, + ...rest +}: ChatSendButtonProps) => { + return ( +
    + +
    + ); +}; + export const ChatUI = ({ onClose, channelId, @@ -19,7 +91,6 @@ export const ChatUI = ({ channelId: string; }) => { const { client, setActiveChannel } = useChatContext(); - const [noted, setNoted] = useState(!!sessionStorage.getItem('noted')); useEffect(() => { const channel = client.channel(CHANNEL_TYPE, channelId); @@ -28,38 +99,27 @@ export const ChatUI = ({ }, [channelId, client, setActiveChannel]); return ( - + } + > -
    -
    - Chat - {/* FIXME: reuse participant list close button */} - + icon="close" + />
    - {!noted && ( -
    - - ℹ️ Messages are currently visible to anyone - with the link and valid session. - - { - sessionStorage.setItem('noted', 'true'); - setNoted(true); - }} - > - Noted - -
    - )}
    - - + +
    ); diff --git a/sample-apps/react/react-dogfood/components/Countdown.tsx b/sample-apps/react/react-dogfood/components/Countdown.tsx deleted file mode 100644 index c92290d71d..0000000000 --- a/sample-apps/react/react-dogfood/components/Countdown.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Box, Stack } from '@mui/material'; - -const PUNCHLINES = [ - 'Countdown to Launching Video. Let’s go! 🚀', - 'Less than 24 hours until launch. Keep Pushing! 💪', - 'Video has been launched! 🎉🚀 Keep Pushing! 💪', -]; - -const formatValue = (value: number) => - value < 10 ? `0${value}` : value.toString(); - -const divMod = (dividend: number, divisor: number) => [ - Math.floor(dividend / divisor), - dividend % divisor, -]; -const calculateTimeLeft = (deadline: number) => { - const now = new Date().getTime(); - const totalSecondsLeft = Math.round((deadline - now) / 1000); - - if (totalSecondsLeft <= 0) - return { totalSecondsLeft: 0, days: 0, hours: 0, minutes: 0, seconds: 0 }; - const [days, minusDaysLeft] = divMod(totalSecondsLeft, 24 * 3600); - const [hours, minusHoursLeft] = divMod(minusDaysLeft, 3600); - const [minutes, seconds] = divMod(minusHoursLeft, 60); - - return { - totalSecondsLeft, - days, - hours, - minutes, - seconds, - }; -}; - -type CountdownProps = { - deadlineTimestamp: number; -}; - -export const Countdown = ({ deadlineTimestamp }: CountdownProps) => { - const [left, setLeft] = useState(calculateTimeLeft(deadlineTimestamp)); - - useEffect(() => { - const interval = setInterval( - () => setLeft(calculateTimeLeft(deadlineTimestamp)), - 1000, - ); - return () => { - clearInterval(interval); - }; - }, [deadlineTimestamp]); - - return ( - - - {left.totalSecondsLeft <= 0 - ? PUNCHLINES[2] - : left.days === 0 - ? PUNCHLINES[1] - : PUNCHLINES[0]} - - 0 ? '#212326' : '#005FFF', - }} - > - - - - - - - ); -}; - -type CounterBoxProps = { - amount: number; - unit: 'days' | 'hours' | 'minutes' | 'seconds'; -}; - -const CounterBox = ({ amount, unit }: CounterBoxProps) => ( - - - {formatValue(amount)} - - - {unit} - - -); diff --git a/sample-apps/react/react-dogfood/components/Debug/DebugParticipantViewUI.tsx b/sample-apps/react/react-dogfood/components/Debug/DebugParticipantViewUI.tsx index 34362e1587..123c02c27b 100644 --- a/sample-apps/react/react-dogfood/components/Debug/DebugParticipantViewUI.tsx +++ b/sample-apps/react/react-dogfood/components/Debug/DebugParticipantViewUI.tsx @@ -1,20 +1,45 @@ +import { useEffect, useState } from 'react'; import { DefaultParticipantViewUI, + GenericMenu, + GenericMenuButtonItem, + Icon, + OwnCapability, + ParticipantActionsContextMenu, + Restricted, + SfuModels, useCall, + useI18n, + useMenuContext, useParticipantViewContext, } from '@stream-io/video-react-sdk'; import { DebugStatsView } from './DebugStatsView'; import { useIsDebugMode } from './useIsDebugMode'; +import { useIsDemoEnvironment } from '../../context/AppEnvironmentContext'; export const DebugParticipantViewUI = () => { const call = useCall(); const { participant } = useParticipantViewContext(); const { sessionId, userId, videoStream } = participant; + + const isDemoEnvironment = useIsDemoEnvironment(); + const participantContextMenuActions = isDemoEnvironment + ? CustomParticipantActionsContextMenu + : ParticipantActionsContextMenu; + const isDebug = useIsDebugMode(); - if (!isDebug) return ; + if (!isDebug) { + return ( + + ); + } return ( <> - +
    { ); }; + +const CustomParticipantActionsContextMenu = () => { + const call = useCall(); + const { t } = useI18n(); + + const { participant, participantViewElement, videoElement } = + useParticipantViewContext(); + const { pin, publishedTracks, sessionId, userId } = participant; + + const hasAudio = publishedTracks.includes(SfuModels.TrackType.AUDIO); + const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO); + const hasScreenShare = publishedTracks.includes( + SfuModels.TrackType.SCREEN_SHARE, + ); + const hasScreenShareAudio = publishedTracks.includes( + SfuModels.TrackType.SCREEN_SHARE_AUDIO, + ); + + const [fullscreenModeOn, setFullscreenModeOn] = useState( + !!document.fullscreenElement, + ); + const [pictureInPictureElement, setPictureInPictureElement] = useState( + document.pictureInPictureElement, + ); + + const blockUser = () => call?.blockUser(userId); + const muteAudio = () => call?.muteUser(userId, 'audio'); + const muteVideo = () => call?.muteUser(userId, 'video'); + const muteScreenShare = () => call?.muteUser(userId, 'screenshare'); + const muteScreenShareAudio = () => + call?.muteUser(userId, 'screenshare_audio'); + + const toggleParticipantPinnedAt = () => { + if (pin) { + call?.unpin(sessionId); + } else { + call?.pin(sessionId); + } + }; + + const pinForEveryone = () => { + call + ?.pinForEveryone({ + user_id: userId, + session_id: sessionId, + }) + .catch((err) => { + console.error(`Failed to pin participant ${userId}`, err); + }); + }; + + const unpinForEveryone = () => { + call + ?.unpinForEveryone({ + user_id: userId, + session_id: sessionId, + }) + .catch((err) => { + console.error(`Failed to unpin participant ${userId}`, err); + }); + }; + + const toggleFullscreenMode = () => { + if (!fullscreenModeOn) { + return participantViewElement + ?.requestFullscreen() + .then(() => setFullscreenModeOn(true)) + .catch(console.error); + } + + document + .exitFullscreen() + .catch(console.error) + .finally(() => setFullscreenModeOn(false)); + }; + + useEffect(() => { + if (!videoElement) return; + + const handlePictureInPicture = () => { + setPictureInPictureElement(document.pictureInPictureElement); + }; + + videoElement.addEventListener( + 'enterpictureinpicture', + handlePictureInPicture, + ); + videoElement.addEventListener( + 'leavepictureinpicture', + handlePictureInPicture, + ); + + return () => { + videoElement.removeEventListener( + 'enterpictureinpicture', + handlePictureInPicture, + ); + videoElement.removeEventListener( + 'leavepictureinpicture', + handlePictureInPicture, + ); + }; + }, [videoElement]); + + const togglePictureInPicture = () => { + if (videoElement && pictureInPictureElement !== videoElement) { + return videoElement + .requestPictureInPicture() + .catch(console.error) as Promise; + } + + document.exitPictureInPicture().catch(console.error); + }; + + const { close } = useMenuContext() || {}; + return ( + + + + {pin ? t('Unpin') : t('Pin')} + + + + + {t('Pin for everyone')} + + + + {t('Unpin for everyone')} + + + + + + {t('Block')} + + + + {hasVideo && ( + + + {t('Turn off video')} + + )} + {hasScreenShare && ( + + + {t('Turn off screen share')} + + )} + {hasAudio && ( + + + {t('Mute audio')} + + )} + {hasScreenShareAudio && ( + + + {t('Mute screen share audio')} + + )} + + {participantViewElement && ( + + {t('{{ direction }} fullscreen', { + direction: fullscreenModeOn ? t('Leave') : t('Enter'), + })} + + )} + {videoElement && document.pictureInPictureEnabled && ( + + {t('{{ direction }} picture-in-picture', { + direction: + pictureInPictureElement === videoElement + ? t('Leave') + : t('Enter'), + })} + + )} + + ); +}; diff --git a/sample-apps/react/react-dogfood/components/Debug/DebugStatsView.tsx b/sample-apps/react/react-dogfood/components/Debug/DebugStatsView.tsx index 2a6f89d903..d14e1bbbd8 100644 --- a/sample-apps/react/react-dogfood/components/Debug/DebugStatsView.tsx +++ b/sample-apps/react/react-dogfood/components/Debug/DebugStatsView.tsx @@ -1,14 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { Call, StatCard, useCallStateHooks } from '@stream-io/video-react-sdk'; -import { - autoUpdate, - flip, - offset, - shift, - size, - useFloating, - UseFloatingData, -} from '@floating-ui/react'; +import { useFloatingUIPreset } from '../../hooks/useFloatingUIPreset'; export const DebugStatsView = (props: { call: Call; @@ -130,42 +122,3 @@ const unwrapStats = (rawStats?: RTCStatsReport) => { }); return decodedStats; }; - -const useFloatingUIPreset = ( - props: Pick, -) => { - const { - refs, - x, - y, - update, - elements: { domReference, floating }, - } = useFloating({ - placement: props.placement, - strategy: props.strategy, - middleware: [ - offset(10), - shift(), - flip(), - size({ - padding: 10, - apply: ({ availableHeight, elements }) => { - Object.assign(elements.floating.style, { - maxHeight: `${availableHeight}px`, - }); - }, - }), - ], - }); - - // handle window resizing - useEffect(() => { - if (!domReference || !floating) return; - const cleanup = autoUpdate(domReference, floating, update); - return () => { - cleanup(); - }; - }, [domReference, floating, update]); - - return { refs, x, y, domReference, floating, strategy: props.strategy }; -}; diff --git a/sample-apps/react/react-dogfood/components/DefaultAppHeader.tsx b/sample-apps/react/react-dogfood/components/DefaultAppHeader.tsx new file mode 100644 index 0000000000..d7254f1b1d --- /dev/null +++ b/sample-apps/react/react-dogfood/components/DefaultAppHeader.tsx @@ -0,0 +1,42 @@ +import clsx from 'clsx'; +import { HomeButton, UserInfo } from './LobbyHeader'; + +import { Icon, useI18n } from '@stream-io/video-react-sdk'; + +export const DefaultAppHeader = (props: { transparent?: boolean }) => { + const { transparent } = props; + const { t } = useI18n(); + return ( + + ); +}; diff --git a/sample-apps/react/react-dogfood/components/DevMenu.tsx b/sample-apps/react/react-dogfood/components/DevMenu.tsx index c9fea63bad..e86a69a8aa 100644 --- a/sample-apps/react/react-dogfood/components/DevMenu.tsx +++ b/sample-apps/react/react-dogfood/components/DevMenu.tsx @@ -1,65 +1,68 @@ -import { useState } from 'react'; -import { useCall, useCallStateHooks } from '@stream-io/video-react-sdk'; -import ConstructionIcon from '@mui/icons-material/Construction'; -import IconButton from '@mui/material/IconButton'; -import DownloadingIcon from '@mui/icons-material/Downloading'; -import QueryStatsIcon from '@mui/icons-material/QueryStats'; -import LiveTvIcon from '@mui/icons-material/LiveTv'; -import CancelPresentationIcon from '@mui/icons-material/CancelPresentation'; -import Menu from '@mui/material/Menu'; -import Divider from '@mui/material/Divider'; -import MenuList from '@mui/material/MenuList'; -import MenuItem from '@mui/material/MenuItem'; -import LanIcon from '@mui/icons-material/Lan'; -import ListItemText from '@mui/material/ListItemText'; -import ListItemIcon from '@mui/material/ListItemIcon'; +import Link from 'next/link'; +import { Icon, useCall, useCallStateHooks } from '@stream-io/video-react-sdk'; export const DevMenu = () => { - const [anchorEl, setAnchorEl] = useState(null); - const open = !!anchorEl; return ( -
    - { - setAnchorEl(e.currentTarget); - }} - > - - - setAnchorEl(null)} - MenuListProps={{ - 'aria-labelledby': 'dev-debug-menu-button', - }} - > - - {/**/} - {/**/} - {/**/} - - - - - - - - - - - - - - -
    +
      +
    • + +
    • +
    • + +
    • + +
    • + +
    • + +
    • +
    • + +
    • +
    • + +
    • + +
    • + +
    • + +
    • +
    • + +
    • +
    • + +
    • + +
    • +
    • + +
    • +
    • +
    • + + + Usage guide + +
    • +
    • + + + Recordings + +
    • +
    ); }; @@ -68,7 +71,8 @@ const StartStopBroadcasting = () => { const { useIsCallHLSBroadcastingInProgress } = useCallStateHooks(); const isBroadcasting = useIsCallHLSBroadcastingInProgress(); return ( - { if (!call) return; if (isBroadcasting) { @@ -87,13 +91,12 @@ const StartStopBroadcasting = () => { } }} > - - {isBroadcasting ? : } - - - {isBroadcasting ? 'Stop broadcasting' : 'Start broadcasting'} - - + + {isBroadcasting ? 'Stop broadcasting' : 'Start broadcasting'} + ); }; @@ -102,7 +105,8 @@ const GoOrStopLive = () => { const { useIsCallLive } = useCallStateHooks(); const isLive = useIsCallLive(); return ( - { if (!call) return; if (isLive) { @@ -121,18 +125,19 @@ const GoOrStopLive = () => { } }} > - - {isLive ? : } - - {isLive ? 'Stop Live' : 'Go Live'} - + + {isLive ? 'Stop Live' : 'Go Live'} + ); }; // const MigrateToNewSfu = () => { // const call = useCall(); // return ( -// +// // ); // }; // @@ -163,7 +168,7 @@ const GoOrStopLive = () => { // const EmulateSFUShuttingDown = () => { // const call = useCall(); // return ( -// { // if (!call) return; // call['dispatcher'].dispatch({ @@ -180,7 +185,7 @@ const GoOrStopLive = () => { // // // Emulate SFU shutdown -// +// // ); // }; @@ -188,64 +193,59 @@ const ConnectToLocalSfu = (props: { port?: number; sfuId?: string }) => { const { port = 3031, sfuId = 'SFU-1' } = props; const params = new URLSearchParams(window.location.search); return ( - { params.set('sfuUrl', `http://127.0.0.1:${port}/twirp`); params.set('sfuWsUrl', `ws://127.0.0.1:${port}/ws`); window.location.search = params.toString(); }} > - - - - Connect to local {sfuId} - + + Connect to local {sfuId} + ); }; const RestartPublisher = () => { const call = useCall(); return ( - + + ICERestart Publisher + ); }; const RestartSubscriber = () => { const call = useCall(); return ( - + + ICERestart Subscriber + ); }; const LogPublisherStats = () => { const call = useCall(); return ( - { if (!call) return; call.publisher?.getStats().then((stats: RTCStatsReport) => { @@ -257,52 +257,17 @@ const LogPublisherStats = () => { }); }} > - - - - Log Publisher stats - - ); -}; - -const StartBroadcastIcon = () => { - return ( - - - - ); -}; - -const EndBroadcastIcon = () => { - return ( - - - + + Log Publisher stats + ); }; const LogSubscriberStats = () => { const call = useCall(); return ( - { if (!call) return; call.subscriber?.getStats().then((stats: RTCStatsReport) => { @@ -314,10 +279,8 @@ const LogSubscriberStats = () => { }); }} > - - - - Log Subscriber stats - + + Log Subscriber stats + ); }; diff --git a/sample-apps/react/react-dogfood/components/Feedback/Feedback.tsx b/sample-apps/react/react-dogfood/components/Feedback/Feedback.tsx new file mode 100644 index 0000000000..3b525a10ed --- /dev/null +++ b/sample-apps/react/react-dogfood/components/Feedback/Feedback.tsx @@ -0,0 +1,261 @@ +import { HTMLInputTypeAttribute, useCallback, useMemo, useState } from 'react'; +import clsx from 'clsx'; + +import { useField, useForm } from 'react-form'; + +import { Icon, useI18n, useMenuContext } from '@stream-io/video-react-sdk'; + +import { getCookie } from '../../helpers/getCookie'; + +export type Props = { + className?: string; + callId?: string; + inMeeting?: boolean; +}; + +function required(value: string | number, name: string) { + if (!value) { + return `Please enter a ${name}`; + } + return false; +} + +const Input = (props: { + className?: string; + type: HTMLInputTypeAttribute; + placeholder: string; + name: string; + required?: boolean; +}) => { + const { name, className, ...rest } = props; + const { + meta: { error, isTouched }, + getInputProps, + } = useField(name, { + validate: props.required + ? (value: string) => required(value, name) + : undefined, + }); + + const rootClassName = clsx(className, { + 'rd__feedback-error': isTouched && error, + }); + + return ; +}; + +const TextArea = (props: { + className?: string; + placeholder: string; + name: string; + required?: boolean; +}) => { + const { name, className, ...rest } = props; + const { + meta: { error, isTouched }, + getInputProps, + } = useField(name, { + validate: props.required + ? (value: string) => required(value, name) + : undefined, + }); + + const rootClassName = clsx('rd__feedback-textarea', { + 'rd__feedback-error': isTouched && error, + }); + + return