Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Transcriptions and translations #994

Merged
merged 99 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
42ba927
Transcription text component
eddsaura Mar 22, 2023
6cc638c
"Transcription in position"
eddsaura Mar 22, 2023
35907c1
Add jigasi
aerrasti Mar 22, 2023
a530b7b
Button conference change transcription
eddsaura Mar 22, 2023
d9d3544
Event just from react
eddsaura Mar 22, 2023
125be99
refactor position function
eddsaura Mar 22, 2023
0a0648c
wip transcription with history
eddsaura Mar 23, 2023
dd2a51d
margin last child
eddsaura Mar 23, 2023
f543208
add translation
aerrasti Mar 24, 2023
4310efe
Merge branch 'main' into feature/jaas-transc
aerrasti Mar 24, 2023
69de14f
fix tests
aerrasti Mar 24, 2023
46bdec0
remove unused import
aerrasti Mar 24, 2023
9990362
WIP transcription history
eddsaura Mar 24, 2023
37c6838
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Mar 24, 2023
5b2d3be
fix conflicts
aerrasti Mar 24, 2023
5a6fdd2
Merge branch 'feature/jaas-transc' of github.com:Stooa/Stooa into fea…
aerrasti Mar 24, 2023
be24eca
Hide transcript participant
aerrasti Mar 24, 2023
02b9873
Change transcription history
aerrasti Mar 24, 2023
72606b1
wip transcription
eddsaura Mar 24, 2023
47c2037
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Mar 24, 2023
30df421
Add default jigasi gc values
aerrasti Mar 24, 2023
88eaea2
change copy
aerrasti Mar 24, 2023
18b2003
Wip translation
eddsaura Mar 24, 2023
ac15941
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Mar 24, 2023
6047b8c
Merge branch 'main' into feature/jaas-transc
aerrasti Mar 27, 2023
58a37b2
adding stop translating
eddsaura Mar 27, 2023
2310cfb
Merge branch 'feature/jaas-transc' of github.com:Stooa/Stooa into fea…
aerrasti Mar 27, 2023
9a24ce9
Position button
eddsaura Mar 27, 2023
b8c36d6
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Mar 27, 2023
2181068
Drilling refactor and enable transcription drawer
eddsaura Mar 28, 2023
acb29c1
Merge branch 'main' into feature/jaas-transc
aerrasti Mar 29, 2023
b330466
Transcription enabled in backend with env
aerrasti Mar 29, 2023
7ddb552
Fixing reviews, selector, name and only if transcr enabled
eddsaura Mar 29, 2023
24256ac
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Mar 29, 2023
3cbb81b
set language at connection and fix coded characters
eddsaura Mar 30, 2023
a94d4c1
Fix enable translating too fast
eddsaura Mar 30, 2023
6e721f7
Add flag check from env
eddsaura Mar 30, 2023
3026744
wip
aerrasti Mar 30, 2023
5bcf0b7
hide history w/o messages
eddsaura Mar 31, 2023
2d2e052
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Mar 31, 2023
9a3440b
change audio transcriber
aerrasti Apr 3, 2023
4e03515
Merge branch 'main' into feature/jaas-transc
aerrasti Apr 3, 2023
a5f328a
Merge branch 'main' into feature/jaas-transc
eddsaura Apr 3, 2023
1d6a451
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Apr 3, 2023
95b886b
Fix function call
eddsaura Apr 3, 2023
0c26bb7
Merge branch 'main' into feature/jaas-transc
eddsaura Apr 5, 2023
93ce2da
wip
eddsaura Apr 6, 2023
458a5ce
Add queue and scroll
eddsaura Apr 6, 2023
26ca731
Wip select language
eddsaura Apr 12, 2023
1463920
WIP Loading
eddsaura Apr 13, 2023
7ce9599
WIP UI Updated
eddsaura Apr 17, 2023
81e5356
Merge branch 'main' into feature/jaas-transc
eddsaura Apr 17, 2023
54a8650
Merge branch 'main' into feature/jaas-transc
aerrasti Apr 18, 2023
c1063aa
WIP UI
eddsaura Apr 18, 2023
885ad5d
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Apr 18, 2023
05b3735
WIP UI cookie
eddsaura Apr 18, 2023
6fc1e14
Unused variables
eddsaura Apr 18, 2023
aac57c0
temporary show transcriber user
aerrasti Apr 20, 2023
d492beb
Remove temporary participant
aerrasti Apr 20, 2023
c501920
change env
aerrasti Apr 20, 2023
b387d76
Reviews UI and modal
eddsaura Apr 20, 2023
f32605d
Merge branch 'feature/jaas-transc' of https://github.com/Stooa/Stooa …
eddsaura Apr 20, 2023
a72fd93
review
eddsaura Apr 20, 2023
d8a7448
Fix typescript
eddsaura Apr 20, 2023
a69fb30
fix position
eddsaura Apr 21, 2023
9a2c228
Fix select, position and toggle
eddsaura Apr 21, 2023
80d9de2
added state
eddsaura Apr 21, 2023
9c4c788
logo in toggle and spacing
eddsaura Apr 21, 2023
c9d6809
wip ui fix
eddsaura Apr 24, 2023
781e161
fix empty caption
eddsaura Apr 24, 2023
d100d4a
fix spacings and color
eddsaura Apr 24, 2023
194564f
Copies
eddsaura Apr 25, 2023
5049158
language selector localized
eddsaura Apr 25, 2023
60b5bb0
Merge branch 'main' into feature/jaas-transc
eddsaura Apr 25, 2023
795679e
fix translation errors
eddsaura Apr 25, 2023
f7cb273
Change jitsi nginx colibri
aerrasti Apr 26, 2023
2280714
change jitsi ngnix config for colibri
aerrasti Apr 26, 2023
678f2fb
Merge branch 'main' into feature/jaas-transc
eddsaura Apr 27, 2023
054115b
fix mobile
eddsaura Apr 27, 2023
aeaf6b8
Merge branch 'main' into feature/jaas-transc
eddsaura Apr 27, 2023
cd71d4d
improve mobile experience
eddsaura Apr 27, 2023
569e879
Unused import
eddsaura Apr 27, 2023
9507704
order selector
eddsaura Apr 27, 2023
09904e9
width title
eddsaura Apr 27, 2023
8ef925c
fix translation
eddsaura May 8, 2023
d066024
Merge branch 'main' into feature/jaas-transc
aerrasti May 10, 2023
0832762
Add jigasi logs
aerrasti May 10, 2023
d2b5522
Merge branch 'main' into feature/jaas-transc
aerrasti May 22, 2023
d3f51f0
Disable transcriptions by default
aerrasti May 22, 2023
b0c89a8
Disabled backend transcriptions
aerrasti May 22, 2023
a5d5285
enable jigasi transcription
aerrasti May 22, 2023
fa68032
Fix token transcription boolean
aerrasti May 23, 2023
86bce22
Merge branch 'main' into feature/jaas-transc
eddsaura Jun 23, 2023
e5822d8
Fix google cloud
eddsaura Jun 23, 2023
af8e5b9
Prettify
eddsaura Jun 26, 2023
01a08ff
rebuild
aerrasti Jun 26, 2023
85890f0
rebuild again
aerrasti Jun 26, 2023
434e52e
Fix transcription selector
eddsaura Jun 26, 2023
7be16a6
Merge branch 'main' into feature/jaas-transc
aerrasti Jun 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ ENABLE_CODEC_VP9=true
ENABLE_CODEC_H264=true
JICOFO_ENABLE_HEALTH_CHECKS=true

# JIGASI
JIGASI_DOMAIN=jigasi.meet.jitsi
JIGASI_SIP_URI=test@sip2sip.info
JIGASI_SIP_PASSWORD=password
JIGASI_SIP_SERVER=sip2sip.info
JIGASI_SIP_PORT=5060
JIGASI_SIP_TRANSPORT=UDP
JIGASI_XMPP_PASSWORD=password
ENABLE_TRANSCRIPTIONS=1
JIGASI_TRANSCRIBER_SEND_TXT=1
JIGASI_TRANSCRIBER_RECORD_AUDIO=0
JIGASI_TRANSCRIBER_ADVERTISE_URL=0

# Google Cloud credentials
GC_PROJECT_ID=project_id
GC_PRIVATE_KEY_ID=private_key_id
GC_PRIVATE_KEY=private_key
GC_CLIENT_EMAIL=client_email
GC_CLIENT_ID=client_id
GC_CLIENT_CERT_URL=client_cert_url

# JWT AUTH
JWT_APP_ID=api_client
JWT_APP_SECRET=this_is_a_secret_key_for_stooa_platform
Expand Down
2 changes: 2 additions & 0 deletions backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ JWT_PASSPHRASE=pass_phrase
JAAS_ACTIVE=false
JAAS_APP_ID=
JAAS_API_KEY=

TRANSCRIPTION_ENABLED=false
1 change: 1 addition & 0 deletions backend/packages/Core/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ services:
bind:
$appId: '%env(JAAS_APP_ID)%'
$apiKey: '%env(JAAS_API_KEY)%'
$transcriptionEnabled: '%env(bool:TRANSCRIPTION_ENABLED)%'

App\Core\Encryption\HalitePasswordEncryption:
bind:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
public function __construct(
private readonly string $appId,
private readonly string $apiKey,
private readonly bool $transcriptionEnabled,
private readonly HostValidator $hostValidator
) {
}
Expand All @@ -36,7 +37,7 @@
return new JWTToken('chat', 'jitsi', $this->appId, '*', $userPayload,
new \DateTimeImmutable('-10 seconds'),
new HeaderPayload($this->apiKey, 'RS256', 'JWT'),
new FeaturesPayload(false, false, false, false, false)
new FeaturesPayload(false, false, $this->transcriptionEnabled, false, false)

Check warning on line 40 in backend/packages/Core/src/JWT/TokenGenerator/JaasTokenGenerator.php

View check run for this annotation

Codecov / codecov/patch

backend/packages/Core/src/JWT/TokenGenerator/JaasTokenGenerator.php#L40

Added line #L40 was not covered by tests
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,26 @@ class JaasTokenGeneratorTest extends TestCase
private User $user;
private string $appId;
private string $apiKey;
private bool $transcriptionEnabled;

protected function setUp(): void
{
parent::setUp();

$this->appId = 'appId';
$this->apiKey = 'apiKey';
$this->transcriptionEnabled = false;

$this->user = UserFactory::createOne()->object();

$this->hostValidator = $this->createStub(HostValidator::class);

$this->jaasTokenGenerator = new JaasTokenGenerator($this->appId, $this->apiKey, $this->hostValidator);
$this->jaasTokenGenerator = new JaasTokenGenerator(
$this->appId,
$this->apiKey,
$this->transcriptionEnabled,
$this->hostValidator
);
}

/** @test */
Expand Down
54 changes: 54 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,60 @@ services:
meet.jitsi:
aliases:
- ${JVB_SERVER}
jigasi:
image: jitsi/jigasi:stable-8319
restart: ${RESTART_POLICY:-unless-stopped}
ports:
- '${JIGASI_PORT_MIN:-20000}-${JIGASI_PORT_MAX:-20050}:${JIGASI_PORT_MIN:-20000}-${JIGASI_PORT_MAX:-20050}/udp'
volumes:
- ${CONFIG}/jigasi:/config:Z
- ${CONFIG}/transcripts:/tmp/transcripts:Z
- ./jitsi/jigasi/custom-sip-communicator.properties:/config/custom-sip-communicator.properties:Z
environment:
- ENABLE_AUTH
- ENABLE_GUESTS
- XMPP_AUTH_DOMAIN
- XMPP_GUEST_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_SERVER
- XMPP_PORT
- XMPP_DOMAIN
- PUBLIC_URL
- JIGASI_DISABLE_SIP
- JIGASI_SIP_URI
- JIGASI_SIP_PASSWORD
- JIGASI_SIP_SERVER
- JIGASI_SIP_PORT
- JIGASI_SIP_TRANSPORT
- JIGASI_SIP_DEFAULT_ROOM
- JIGASI_XMPP_USER
- JIGASI_XMPP_PASSWORD
- JIGASI_BREWERY_MUC
- JIGASI_PORT_MIN
- JIGASI_PORT_MAX
- JIGASI_HEALTH_CHECK_SIP_URI
- JIGASI_HEALTH_CHECK_INTERVAL
- JIGASI_SIP_KEEP_ALIVE_METHOD
- JIGASI_ENABLE_SDES_SRTP
- ENABLE_TRANSCRIPTIONS
- JIGASI_TRANSCRIBER_ADVERTISE_URL
- JIGASI_TRANSCRIBER_RECORD_AUDIO
- JIGASI_TRANSCRIBER_SEND_TXT
- GC_PROJECT_ID
- GC_PRIVATE_KEY_ID
- GC_PRIVATE_KEY
- GC_CLIENT_EMAIL
- GC_CLIENT_ID
- GC_CLIENT_CERT_URL
- SENTRY_DSN="${JIGASI_SENTRY_DSN:-0}"
- SENTRY_ENVIRONMENT
- SENTRY_RELEASE
- TZ
depends_on:
- xmpp
networks:
meet.jitsi:
jitsi-nginx:
image: nginx:1.23
depends_on:
Expand Down
1 change: 1 addition & 0 deletions frontend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ NEXT_PUBLIC_XMPP_MUC_DOMAIN=muc.meet.jitsi
NEXT_PUBLIC_XMPP_AUTH_DOMAIN=auth.meet.jitsi
NEXT_PUBLIC_JITSI_ROOM_PREFIX=
NEXT_PUBLIC_ENVIRONMENT=dev
NEXT_PUBLIC_TRANSCRIPTIONS_ENABLED=true
10 changes: 10 additions & 0 deletions frontend/locales/ca/fishbowl.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,15 @@
"during": "Durant",
"duringTooltip": "Feedback rebut durant el fishbowl"
}
},
"transcription" : {
"title": "Transcripció",
"disable": "Ocultar transcripció",
"enable": "Mostrar transcripció",
"loading": "Transcripció carregant...",
"modalTitle": "En quin idioma parles?",
"modalBody": "Abans de començar, necessitem que <span>indiquis en quin idioma participaràs</span> en aquesta conversa.",
"tooltip": "Si us plau, indica quin idioma parles.",
"translate": "Traduir"
}
}
10 changes: 10 additions & 0 deletions frontend/locales/en/fishbowl.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,15 @@
"during": "During",
"duringTooltip": "Feedback received during the fishbowl"
}
},
"transcription" : {
"title": "Transcription",
"disable": "Disable transcription",
"enable": "Enable transcription",
"loading": "Transcription loading...",
"modalTitle": "What language do you speak?",
"modalBody": "Before we start, we need you to <span>indicate in which language you will be participating</span> in this conversation.",
"tooltip": "Please specify what language you speak.",
"translate": "Translate"
}
}
10 changes: 10 additions & 0 deletions frontend/locales/es/fishbowl.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,15 @@
"during": "Durante",
"duringTooltip": "Feedback recibido durante el fishbowl."
}
},
"transcription" : {
"title": "Transcripción",
"disable": "Ocultar transcripción",
"enable": "Mostrar transcripción",
"loading": "Cargando transcripción...",
"modalTitle": "¿En qué idioma hablas?",
"modalBody": "Antes de comenzar, necesitamos que <span>indiques en qué idioma participarás</span> en esta conversación.",
"tooltip": "Por favor, indica qué idioma hablas.",
"translate": "Traducir"
}
}
10 changes: 10 additions & 0 deletions frontend/locales/fr/fishbowl.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,5 +198,15 @@
"during": "During",
"duringTooltip": "Feedback received during the fishbowl"
}
},
"transcription" : {
"title" : "Transcription",
"disable": "Cacher la transcription",
"enable": "Afficher la transcription",
"loading": "Transcription du chargement...",
"modalTitle": "Dans quelle langue parlez-vous?",
"modalBody": "Avant de commencer, nous avons besoin que vous <span>indiquiez dans quelle langue vous participerez</span> à cette conversation.",
"tooltip": "Merci d'indiquer la langue que vous parlez.",
"translate": "Traduire"
}
}
75 changes: 72 additions & 3 deletions frontend/src/components/App/ButtonMoreOptions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import VideoIcon from '@/ui/svg/video.svg';
import CheckIcon from '@/ui/svg/checkmark.svg';
import Cross from '@/ui/svg/cross.svg';
import TranscriptionSVG from '@/ui/svg/transcription-icon.svg';
import PermissionsAlert from '@/ui/svg/permissions-alert.svg';

import {
Expand All @@ -36,6 +37,8 @@
import { useModals } from '@/contexts/ModalsContext';
import { useNavigatorType } from '@/hooks/useNavigatorType';
import { supportsCaptureHandle } from '@/lib/helpers';
import { useConference } from '@/jitsi/useConference';
import { useUserAuth } from '@/user/auth/useUserAuth';

interface Props {
unlabeled?: boolean;
Expand All @@ -52,8 +55,13 @@
ref
) => {
const [showDevices, setShowDevices] = useState(false);
const { setShowStopRecording, showStartRecording, setShowStartRecording, setShowFeedbackForm } =
useModals();
const {
setShowStopRecording,
setShowTranscriptionModal,
showStartRecording,
setShowStartRecording,
setShowFeedbackForm
} = useModals();
const { deviceType } = useNavigatorType();

const {
Expand All @@ -67,10 +75,36 @@
permissions
} = useDevices();

const { isModerator, isRecording, feedbackAlert, gaveFeedback } = useStooa();
const {
isModerator,
isRecording,
feedbackAlert,
gaveFeedback,
isTranscriptionEnabled,
setIsTranscriptionEnabled,
setParticipantsActive,
isTranscriberJoined
} = useStooa();

const { stopTranscriptionEvent, startTranscriptionEvent, setConferenceTranscriptionLanguage } =
useConference();

const { getTranscriptionLanguageCookie } = useUserAuth();

const { t } = useTranslation('fishbowl');

const getTranscriptionText = () => {
if (isTranscriptionEnabled && !isTranscriberJoined) {
return t('transcription.loading');

Check warning on line 98 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L98

Added line #L98 was not covered by tests
}

if (isTranscriptionEnabled) {
return t('transcription.disable');

Check warning on line 102 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L102

Added line #L102 was not covered by tests
} else {
return t('transcription.enable');
}
};

const handleAudioInput = (event: React.MouseEvent) => {
const { value } = event.target as HTMLButtonElement;
selectAudioInputDevice(value);
Expand Down Expand Up @@ -106,6 +140,28 @@
setShowFeedbackForm(current => !current);
};

const handleTranscriptionToggle = () => {
const transcriptionCookie = getTranscriptionLanguageCookie();

Check warning on line 144 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L144

Added line #L144 was not covered by tests

if (!transcriptionCookie) {
setShowTranscriptionModal(true);
return;

Check warning on line 148 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L147-L148

Added lines #L147 - L148 were not covered by tests
}

if (isTranscriptionEnabled) {
stopTranscriptionEvent();
setIsTranscriptionEnabled(false);
} else {
startTranscriptionEvent();
setConferenceTranscriptionLanguage(transcriptionCookie);

Check warning on line 156 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L152-L156

Added lines #L152 - L156 were not covered by tests
if (deviceType === 'Desktop') {
setParticipantsActive(true);

Check warning on line 158 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L158

Added line #L158 was not covered by tests
}
setIsTranscriptionEnabled(true);
setShowTranscriptionModal(false);

Check warning on line 161 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L160-L161

Added lines #L160 - L161 were not covered by tests
}
};

// Used imperativeHandle to make the parents able to call the handleShowDevices
// this way we keep the state inside the button config component
useImperativeHandle(ref, () => ({
Expand Down Expand Up @@ -160,6 +216,19 @@
{t('feedback.title')}
</button>
)}

{!prejoin && process.env.NEXT_PUBLIC_TRANSCRIPTIONS_ENABLED === 'true' && (
<button
disabled={gaveFeedback}
data-testid="transcription-button"
className="sticky-button sticky-button--transcription"
onClick={() => handleTranscriptionToggle()}

Check warning on line 225 in frontend/src/components/App/ButtonMoreOptions/index.tsx

View check run for this annotation

Codecov / codecov/patch

frontend/src/components/App/ButtonMoreOptions/index.tsx#L225

Added line #L225 was not covered by tests
>
<TranscriptionSVG />
{getTranscriptionText()}
</button>
)}

{isModerator && supportsCaptureHandle() && deviceType === 'Desktop' && !prejoin && (
<button
data-testid="recording-button"
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/App/ButtonMoreOptions/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,13 @@ const Selector = styled.div`
background-color: ${COLOR_NEUTRO_100};
transition: background-color 0.2s ease-in-out;

border-bottom: 1px solid ${COLOR_NEUTRO_600};

& .sticky-button {
width: 100%;
padding: ${space()} ${space(2)};
display: flex;
align-items: center;
border-bottom: 1px solid ${COLOR_NEUTRO_600};

text-align: left;

Expand Down
Loading
Loading