Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d709df305 | |||
| c81047d386 | |||
| 9e91bbe417 | |||
| e821982353 | |||
| 18f4a117ab | |||
| b5b99d4c52 | |||
| 8a4323aa82 | |||
| 5cbac23f13 | |||
| d55810784d | |||
| c2fdefe7b2 | |||
| bf2474d0e1 | |||
| 558d844ef3 | |||
| 94aa828d87 | |||
| 110e062e56 | |||
| f7c2beb30d | |||
| 5bf1456200 | |||
| 80e5c9bb95 | |||
| 74164b8498 | |||
| 82cc2e5d17 |
@@ -119,9 +119,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: numerique-gouv/action-argocd-webhook-notification@main
|
||||
id: notify
|
||||
with:
|
||||
deployment_repo_path: "${{ secrets.DEPLOYMENT_REPO_URL }}"
|
||||
argocd_webhook_secret: "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}"
|
||||
argocd_url: "${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}"
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Call argocd github webhook
|
||||
run: |
|
||||
data='{"ref": "'$GITHUB_REF'","repository": {"html_url":"'$GITHUB_SERVER_URL'/numerique-gouv/lasuite-deploiement"}}'
|
||||
sig=$(echo -n ${data} | openssl dgst -sha1 -hmac "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}" | awk '{print "X-Hub-Signature: sha1="$2}')
|
||||
curl -X POST -H 'X-GitHub-Event:push' -H "Content-Type: application/json" -H "${sig}" --data "${data}" ${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "secrets"]
|
||||
path = secrets
|
||||
url = ../secrets
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 DINUM/Etalab
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM livekit/livekit-server:latest
|
||||
FROM livekit/livekit-server:v1.8.0
|
||||
|
||||
# We inject the nip.io certificate manually because the livekit chart doesn't support volume mounting
|
||||
COPY rootCA.pem /etc/ssl/certs/
|
||||
|
||||
@@ -212,7 +212,7 @@ POSTGRES_PASSWORD: pass
|
||||
Now you are ready to deploy Visio without AI. AI required more dependencies (Openai-compliant API, LiveKit Egress, Cold storage and a docs deployment to push resumes). To deploy meet you need to provide all previous information to the helm chart.
|
||||
|
||||
```
|
||||
$ helm repo add meet https://numerique-gouv.github.io/meet/
|
||||
$ helm repo add meet https://suitenumerique.github.io/meet/
|
||||
$ helm repo update
|
||||
$ helm install meet meet/meet -f examples/meet.values.yaml
|
||||
```
|
||||
|
||||
-1
Submodule secrets deleted from 2ba12db71d
@@ -12,7 +12,12 @@ from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from livekit.api import LiveKitAPI, SendDataRequest, TwirpError # pylint: disable=E0611
|
||||
from livekit.api import ( # pylint: disable=E0611
|
||||
ListRoomsRequest,
|
||||
LiveKitAPI,
|
||||
SendDataRequest,
|
||||
TwirpError,
|
||||
)
|
||||
|
||||
from core import models, utils
|
||||
|
||||
@@ -221,6 +226,12 @@ class LobbyService:
|
||||
color=color,
|
||||
)
|
||||
|
||||
try:
|
||||
self.notify_participants(room_id=room_id)
|
||||
except LobbyNotificationError:
|
||||
# If room not created yet, there is no participants to notify
|
||||
pass
|
||||
|
||||
cache_key = self._get_cache_key(room_id, participant_id)
|
||||
cache.set(
|
||||
cache_key,
|
||||
@@ -228,12 +239,6 @@ class LobbyService:
|
||||
timeout=settings.LOBBY_WAITING_TIMEOUT,
|
||||
)
|
||||
|
||||
try:
|
||||
self.notify_participants(room_id=room_id)
|
||||
except LobbyNotificationError:
|
||||
# If room not created yet, there is no participants to notify
|
||||
pass
|
||||
|
||||
return participant
|
||||
|
||||
def _get_participant(
|
||||
@@ -343,7 +348,18 @@ class LobbyService:
|
||||
}
|
||||
|
||||
lkapi = LiveKitAPI(**settings.LIVEKIT_CONFIGURATION)
|
||||
|
||||
try:
|
||||
room_response = await lkapi.room.list_rooms(
|
||||
ListRoomsRequest(
|
||||
names=[str(room_id)],
|
||||
)
|
||||
)
|
||||
|
||||
# Check if the room exists
|
||||
if not room_response.rooms:
|
||||
return
|
||||
|
||||
await lkapi.room.send_data(
|
||||
SendDataRequest(
|
||||
room=str(room_id),
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Generate Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Generate Document</h2>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Generate PDF</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -776,6 +776,43 @@ def test_update_participant_status_success(mock_cache, lobby_service, participan
|
||||
lobby_service._get_cache_key.assert_called_once_with(room.id, participant_id)
|
||||
|
||||
|
||||
@mock.patch("core.services.lobby.LiveKitAPI")
|
||||
def test_notify_participants_success_no_room(mock_livekit_api, lobby_service):
|
||||
"""Test the notify_participants method when the LiveKit room doesn't exist yet."""
|
||||
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
|
||||
|
||||
# Set up the mock LiveKitAPI and its behavior
|
||||
mock_api_instance = mock.Mock()
|
||||
mock_api_instance.room = mock.Mock()
|
||||
mock_api_instance.room.send_data = mock.AsyncMock()
|
||||
|
||||
# Create a proper response object with an empty rooms list
|
||||
class MockResponse:
|
||||
"""LiveKit API response mock with empty rooms list."""
|
||||
|
||||
rooms = []
|
||||
|
||||
mock_api_instance.room.list_rooms = mock.AsyncMock(return_value=MockResponse())
|
||||
mock_api_instance.aclose = mock.AsyncMock()
|
||||
mock_livekit_api.return_value = mock_api_instance
|
||||
|
||||
# Act
|
||||
lobby_service.notify_participants(room.id)
|
||||
|
||||
# Assert
|
||||
# Verify the API was initialized with correct configuration
|
||||
mock_livekit_api.assert_called_once_with(**settings.LIVEKIT_CONFIGURATION)
|
||||
|
||||
# Verify that the service checked for existing rooms
|
||||
mock_api_instance.room.list_rooms.assert_called_once()
|
||||
|
||||
# Verify the send_data method was not called since no room exists
|
||||
mock_api_instance.room.send_data.assert_not_called()
|
||||
|
||||
# Verify the connection was properly closed
|
||||
mock_api_instance.aclose.assert_called_once()
|
||||
|
||||
|
||||
@mock.patch("core.services.lobby.LiveKitAPI")
|
||||
def test_notify_participants_success(mock_livekit_api, lobby_service):
|
||||
"""Test successful participant notification."""
|
||||
@@ -784,6 +821,14 @@ def test_notify_participants_success(mock_livekit_api, lobby_service):
|
||||
mock_api_instance = mock.Mock()
|
||||
mock_api_instance.room = mock.Mock()
|
||||
mock_api_instance.room.send_data = mock.AsyncMock()
|
||||
|
||||
class MockResponse:
|
||||
"""LiveKit API response mock with empty rooms list."""
|
||||
|
||||
rooms = ["room-1"]
|
||||
|
||||
mock_api_instance.room.list_rooms = mock.AsyncMock(return_value=MockResponse())
|
||||
|
||||
mock_api_instance.aclose = mock.AsyncMock()
|
||||
mock_livekit_api.return_value = mock_api_instance
|
||||
|
||||
@@ -793,6 +838,9 @@ def test_notify_participants_success(mock_livekit_api, lobby_service):
|
||||
# Verify the API was called correctly
|
||||
mock_livekit_api.assert_called_once_with(**settings.LIVEKIT_CONFIGURATION)
|
||||
|
||||
# Verify that the service checked for existing rooms
|
||||
mock_api_instance.room.list_rooms.assert_called_once()
|
||||
|
||||
# Verify the send_data method was called
|
||||
mock_api_instance.room.send_data.assert_called_once()
|
||||
send_data_request = mock_api_instance.room.send_data.call_args[0][0]
|
||||
@@ -817,6 +865,14 @@ def test_notify_participants_error(mock_livekit_api, lobby_service):
|
||||
mock_api_instance.room.send_data = mock.AsyncMock(
|
||||
side_effect=TwirpError(msg="test error", code=123)
|
||||
)
|
||||
|
||||
class MockResponse:
|
||||
"""LiveKit API response mock with empty rooms list."""
|
||||
|
||||
rooms = ["room-1"]
|
||||
|
||||
mock_api_instance.room.list_rooms = mock.AsyncMock(return_value=MockResponse())
|
||||
|
||||
mock_api_instance.aclose = mock.AsyncMock()
|
||||
mock_livekit_api.return_value = mock_api_instance
|
||||
|
||||
@@ -829,6 +885,9 @@ def test_notify_participants_error(mock_livekit_api, lobby_service):
|
||||
# Verify the API was called correctly
|
||||
mock_livekit_api.assert_called_once_with(**settings.LIVEKIT_CONFIGURATION)
|
||||
|
||||
# Verify that the service checked for existing rooms
|
||||
mock_api_instance.room.list_rooms.assert_called_once()
|
||||
|
||||
# Verify send_data was called
|
||||
mock_api_instance.room.send_data.assert_called_once()
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
import sentry_sdk
|
||||
from configurations import Configuration, values
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.logging import ignore_logger
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -314,7 +315,6 @@ class Base(Configuration):
|
||||
EMAIL_FROM = values.Value("from@example.com")
|
||||
|
||||
AUTH_USER_MODEL = "core.User"
|
||||
INVITATION_VALIDITY_DURATION = 604800 # 7 days, in seconds
|
||||
|
||||
# CORS
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
@@ -576,6 +576,9 @@ class Base(Configuration):
|
||||
scope = sentry_sdk.get_global_scope()
|
||||
scope.set_tag("application", "backend")
|
||||
|
||||
# Ignore the logs added by the DockerflowMiddleware
|
||||
ignore_logger("request.summary")
|
||||
|
||||
|
||||
class Build(Base):
|
||||
"""Settings used when the application is built.
|
||||
|
||||
+10
-11
@@ -25,7 +25,7 @@ license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"boto3==1.37.4",
|
||||
"boto3==1.37.18",
|
||||
"Brotli==1.1.0",
|
||||
"brevo-python==1.1.2",
|
||||
"celery[redis]==5.4.0",
|
||||
@@ -48,17 +48,16 @@ dependencies = [
|
||||
"june-analytics-python==2.3.0",
|
||||
"markdown==3.7",
|
||||
"nested-multipart-parser==1.5.0",
|
||||
"psycopg[binary]==3.2.5",
|
||||
"psycopg[binary]==3.2.6",
|
||||
"PyJWT==2.10.1",
|
||||
"python-frontmatter==1.1.0",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk==2.22.0",
|
||||
"sentry-sdk==2.24.1",
|
||||
"url-normalize==1.4.3",
|
||||
"WeasyPrint>=60.2",
|
||||
"whitenoise==6.9.0",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
"livekit-api==0.8.2",
|
||||
"aiohttp==3.11.13",
|
||||
"aiohttp==3.11.14",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -73,18 +72,18 @@ dev = [
|
||||
"drf-spectacular-sidecar==2025.3.1",
|
||||
"freezegun==1.5.1",
|
||||
"ipdb==0.13.13",
|
||||
"ipython==9.0.1",
|
||||
"pyfakefs==5.7.4",
|
||||
"ipython==9.0.2",
|
||||
"pyfakefs==5.8.0",
|
||||
"pylint-django==2.6.1",
|
||||
"pylint==3.3.4",
|
||||
"pylint==3.3.6",
|
||||
"pytest-cov==6.0.0",
|
||||
"pytest-django==4.10.0",
|
||||
"pytest==8.3.5",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.6.1",
|
||||
"responses==0.25.6",
|
||||
"ruff==0.9.9",
|
||||
"types-requests==2.32.0.20250301",
|
||||
"responses==0.25.7",
|
||||
"ruff==0.11.2",
|
||||
"types-requests==2.32.0.20250306",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -35,40 +35,7 @@ const config: Config = {
|
||||
exclude: [],
|
||||
jsxFramework: 'react',
|
||||
outdir: 'src/styled-system',
|
||||
globalFontface: {
|
||||
Marianne: [
|
||||
{
|
||||
src: 'url(/fonts/Marianne-Regular-subset.woff2) format("woff2")',
|
||||
fontWeight: 400,
|
||||
fontStyle: 'normal',
|
||||
fontDisplay: 'swap',
|
||||
},
|
||||
{
|
||||
path: 'url(/fonts/Marianne-Regular_Italic-subset.woff2) format("woff2")',
|
||||
fontWeight: 400,
|
||||
fontStyle: 'italic',
|
||||
fontDisplay: 'swap',
|
||||
},
|
||||
{
|
||||
path: 'url(/fonts/Marianne-Medium-subset.woff2) format("woff2")',
|
||||
fontWeight: 500,
|
||||
fontStyle: 'normal',
|
||||
fontDisplay: 'swap',
|
||||
},
|
||||
{
|
||||
path: 'url(/fonts/Marianne-Bold-subset.woff2) format("woff2")',
|
||||
fontWeight: 700,
|
||||
fontStyle: 'normal',
|
||||
fontDisplay: 'swap',
|
||||
},
|
||||
{
|
||||
path: 'url(/fonts/Marianne-ExtraBold-subset.woff2) format("woff2")',
|
||||
fontWeight: 800,
|
||||
fontStyle: 'normal',
|
||||
fontDisplay: 'swap',
|
||||
},
|
||||
],
|
||||
},
|
||||
globalFontface: {},
|
||||
theme: {
|
||||
...pandaPreset.theme,
|
||||
// media queries are defined in em so that zooming with text-only mode triggers breakpoints
|
||||
@@ -223,6 +190,7 @@ const config: Config = {
|
||||
fonts: {
|
||||
sans: {
|
||||
value: [
|
||||
'Marianne',
|
||||
'Source Sans',
|
||||
'Source Sans fallback',
|
||||
'ui-sans-serif',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cva, RecipeVariantProps } from '@/styled-system/css'
|
||||
import { css, cva, RecipeVariantProps } from '@/styled-system/css'
|
||||
import React from 'react'
|
||||
|
||||
const avatar = cva({
|
||||
@@ -19,7 +19,8 @@ const avatar = cva({
|
||||
list: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
fontSize: '1.25rem',
|
||||
fontSize: '1.3rem',
|
||||
lineHeight: '1rem',
|
||||
},
|
||||
placeholder: {
|
||||
width: '100%',
|
||||
@@ -60,7 +61,13 @@ export const Avatar = ({
|
||||
className={avatar({ context, notification })}
|
||||
{...props}
|
||||
>
|
||||
{initial}
|
||||
<span
|
||||
className={css({
|
||||
marginTop: '-0.3rem',
|
||||
})}
|
||||
>
|
||||
{initial}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ export const FeedbackBanner = () => {
|
||||
})}
|
||||
>
|
||||
<RiErrorWarningLine size={20} aria-hidden="true" />
|
||||
<Text as="p">{t('feedback.context')}</Text>
|
||||
<Text as="p" variant="sm">
|
||||
{t('feedback.context')}
|
||||
</Text>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
@@ -33,7 +35,7 @@ export const FeedbackBanner = () => {
|
||||
gap: 0.25,
|
||||
})}
|
||||
>
|
||||
<A href={GRIST_FORM} target="_blank">
|
||||
<A href={GRIST_FORM} target="_blank" size="sm">
|
||||
{t('feedback.cta')}
|
||||
</A>
|
||||
<RiExternalLinkLine size={16} aria-hidden="true" />
|
||||
|
||||
@@ -17,12 +17,13 @@ const Heading = styled('h2', {
|
||||
base: {
|
||||
width: 'fit-content',
|
||||
marginBottom: 0,
|
||||
fontSize: '1.5rem',
|
||||
fontSize: '1.3rem',
|
||||
fontWeight: '700',
|
||||
marginTop: '0.75rem',
|
||||
lineHeight: '2rem',
|
||||
lineHeight: '1.7rem',
|
||||
maxWidth: '23rem',
|
||||
textAlign: 'center',
|
||||
textWrap: 'pretty',
|
||||
textWrap: 'balance',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -31,7 +32,7 @@ const Body = styled('p', {
|
||||
maxWidth: '23rem',
|
||||
textAlign: 'center',
|
||||
textWrap: 'pretty',
|
||||
lineHeight: '1.2rem',
|
||||
lineHeight: '1.4rem',
|
||||
fontSize: '1rem',
|
||||
},
|
||||
})
|
||||
@@ -102,7 +103,7 @@ const Slide = styled('div', {
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
justifyContent: 'start',
|
||||
minHeight: { base: 'none', xsm: '550px' },
|
||||
minHeight: { base: 'none', xsm: '580px' },
|
||||
minWidth: { base: 'none', xsm: '200px' },
|
||||
width: { base: '100%', xsm: '22.625rem' },
|
||||
},
|
||||
|
||||
@@ -72,8 +72,8 @@ const LeftColumn = ({ children }: { children?: ReactNode }) => {
|
||||
textAlign: 'left',
|
||||
alignItems: 'flex-start',
|
||||
flexShrink: '1',
|
||||
flexBasis: '36rem',
|
||||
maxWidth: '36rem',
|
||||
flexBasis: '40rem',
|
||||
maxWidth: '40rem',
|
||||
padding: '1em 3em',
|
||||
},
|
||||
})}
|
||||
@@ -120,14 +120,13 @@ const Separator = styled('div', {
|
||||
|
||||
const Heading = styled('h1', {
|
||||
base: {
|
||||
fontWeight: '500',
|
||||
fontWeight: '700',
|
||||
fontStyle: 'normal',
|
||||
fontStretch: 'normal',
|
||||
fontOpticalSizing: 'auto',
|
||||
marginBottom: 0,
|
||||
paddingBottom: '0.75rem',
|
||||
paddingBottom: '1.2rem',
|
||||
fontSize: '2.3rem',
|
||||
lineHeight: '2.5rem',
|
||||
lineHeight: '2.6rem',
|
||||
letterSpacing: '0',
|
||||
xsm: {
|
||||
fontSize: '3rem',
|
||||
@@ -139,9 +138,9 @@ const Heading = styled('h1', {
|
||||
const IntroText = styled('div', {
|
||||
base: {
|
||||
marginBottom: '3rem',
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: '1.8rem',
|
||||
textWrap: 'pretty',
|
||||
fontSize: '1.2rem',
|
||||
lineHeight: '1.5rem',
|
||||
textWrap: 'balance',
|
||||
maxWidth: '32rem',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -37,7 +37,14 @@ export const Admin = () => {
|
||||
flexDirection="column"
|
||||
alignItems="start"
|
||||
>
|
||||
<Text variant="note" wrap="pretty" margin="md">
|
||||
<Text
|
||||
variant="note"
|
||||
wrap="pretty"
|
||||
className={css({
|
||||
textStyle: 'sm',
|
||||
})}
|
||||
margin={'md'}
|
||||
>
|
||||
{t('description')}
|
||||
</Text>
|
||||
<RACSeparator
|
||||
@@ -68,8 +75,8 @@ export const Admin = () => {
|
||||
</Text>
|
||||
<Field
|
||||
type="radioGroup"
|
||||
label="Type d'accès à la réunion"
|
||||
aria-label="Type d'accès à la réunion"
|
||||
label={t('access.type')}
|
||||
aria-label={t('access.type')}
|
||||
labelProps={{
|
||||
className: css({
|
||||
fontSize: '1rem',
|
||||
|
||||
@@ -75,7 +75,7 @@ export function FloatingReaction({
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
lineHeight: '45px',
|
||||
lineHeight: '57px',
|
||||
})}
|
||||
>
|
||||
{emoji}
|
||||
@@ -89,9 +89,10 @@ export function FloatingReaction({
|
||||
textAlign: 'center',
|
||||
borderRadius: '20px',
|
||||
paddingX: '0.5rem',
|
||||
paddingY: '0.15rem',
|
||||
paddingBottom: '0.3125rem',
|
||||
paddingTop: '0.15rem',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
||||
lineHeight: '14px',
|
||||
lineHeight: '16px',
|
||||
})}
|
||||
>
|
||||
{name}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { A, Button, Dialog, P } from '@/primitives'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { css } from '@/styled-system/css'
|
||||
|
||||
// todo - refactor it into a generic system
|
||||
export const ScreenShareErrorModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'error.screenShare' })
|
||||
const isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isOpen}
|
||||
role="alertdialog"
|
||||
title={t('title')}
|
||||
aria-label={t('ariaLabel')}
|
||||
onClose={onClose}
|
||||
>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<>
|
||||
<P>
|
||||
{t('message')}{' '}
|
||||
{isMac && (
|
||||
<>
|
||||
{t('macInstructions')}{' '}
|
||||
<A
|
||||
href="x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
|
||||
color="primary"
|
||||
aria-label={t('macSystemPreferences') + '-' + t('newTab')}
|
||||
>
|
||||
{t('macSystemPreferences')}
|
||||
</A>
|
||||
.{' '}
|
||||
</>
|
||||
)}
|
||||
{t('helpLinkText')}{' '}
|
||||
<A
|
||||
href="https://lasuite.crisp.help/fr/article/visio-probleme-de-presentation-1xkf799/"
|
||||
aria-label={t('helpLinkLabel') + '-' + t('newTab')}
|
||||
target="_blank"
|
||||
color="primary"
|
||||
>
|
||||
{t('helpLinkLabel')}
|
||||
</A>
|
||||
.
|
||||
</P>
|
||||
<Button
|
||||
onPress={close}
|
||||
size="sm"
|
||||
variant="primary"
|
||||
className={css({ marginLeft: 'auto', marginTop: '2rem' })}
|
||||
>
|
||||
{t('closeButton')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ import {
|
||||
/**
|
||||
* This is simply a wrapper around track-processor-js Processor
|
||||
* in order to be compatible with a common interface BackgroundBlurProcessorInterface
|
||||
* used accross the project.
|
||||
* used across the project.
|
||||
*/
|
||||
export class BackgroundBlurTrackProcessorJsWrapper
|
||||
implements BackgroundProcessorInterface
|
||||
|
||||
@@ -13,6 +13,7 @@ export const Effects = () => {
|
||||
<div
|
||||
className={css({
|
||||
padding: '0 1.5rem',
|
||||
overflowY: 'scroll',
|
||||
})}
|
||||
>
|
||||
<EffectsConfiguration
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@livekit/components-core'
|
||||
import { RoomEvent, Track } from 'livekit-client'
|
||||
import * as React from 'react'
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
CarouselLayout,
|
||||
ConnectionStateToast,
|
||||
@@ -29,6 +29,7 @@ import { ParticipantTile } from '../components/ParticipantTile'
|
||||
import { SidePanel } from '../components/SidePanel'
|
||||
import { useSidePanel } from '../hooks/useSidePanel'
|
||||
import { RecordingStateToast } from '../components/RecordingStateToast'
|
||||
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
||||
|
||||
const LayoutWrapper = styled(
|
||||
'div',
|
||||
@@ -149,6 +150,8 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
|
||||
const { isSidePanelOpen } = useSidePanel()
|
||||
|
||||
const [isShareErrorVisible, setIsShareErrorVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="lk-video-conference"
|
||||
@@ -162,6 +165,10 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
value={layoutContext}
|
||||
// onPinChange={handleFocusStateChange}
|
||||
>
|
||||
<ScreenShareErrorModal
|
||||
isOpen={isShareErrorVisible}
|
||||
onClose={() => setIsShareErrorVisible(false)}
|
||||
/>
|
||||
<div
|
||||
// todo - extract these magic values into constant
|
||||
style={{
|
||||
@@ -207,7 +214,18 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
||||
</LayoutWrapper>
|
||||
<MainNotificationToast />
|
||||
</div>
|
||||
<ControlBar />
|
||||
<ControlBar
|
||||
onDeviceError={(e) => {
|
||||
console.error(e)
|
||||
if (
|
||||
e.source == Track.Source.ScreenShare &&
|
||||
e.error.toString() ==
|
||||
'NotAllowedError: Permission denied by system'
|
||||
) {
|
||||
setIsShareErrorVisible(true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<SidePanel />
|
||||
</LayoutContextProvider>
|
||||
)}
|
||||
|
||||
@@ -106,7 +106,7 @@ const Marianne = () => {
|
||||
className={css({
|
||||
letterSpacing: '-.01em',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '600',
|
||||
fontWeight: '700',
|
||||
fontFamily: 'Marianne',
|
||||
fontSize: '1.25rem',
|
||||
lineHeight: '1.75rem',
|
||||
|
||||
@@ -44,7 +44,7 @@ const Marianne = () => {
|
||||
className={css({
|
||||
letterSpacing: '-.01em',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '600',
|
||||
fontWeight: '700',
|
||||
fontFamily: 'Marianne',
|
||||
fontSize: '0.7875rem !important',
|
||||
})}
|
||||
|
||||
@@ -60,6 +60,17 @@
|
||||
"createRoom": {
|
||||
"heading": "",
|
||||
"body": ""
|
||||
},
|
||||
"screenShare": {
|
||||
"title": "",
|
||||
"ariaLabel": "",
|
||||
"message": "",
|
||||
"macInstructions": "",
|
||||
"macSystemPreferences": "",
|
||||
"helpLinkText": "",
|
||||
"helpLinkLabel": "",
|
||||
"closeButton": "",
|
||||
"newTab": ""
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
@@ -178,6 +189,7 @@
|
||||
"access": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"type": "",
|
||||
"levels": {
|
||||
"public": {
|
||||
"label": "",
|
||||
|
||||
@@ -61,6 +61,17 @@
|
||||
"createRoom": {
|
||||
"heading": "Authentication Required",
|
||||
"body": "This room has not been created yet. Please authenticate to create it or wait for an authenticated user to do so."
|
||||
},
|
||||
"screenShare": {
|
||||
"title": "Unable to share your screen",
|
||||
"ariaLabel": "Unable to share your screen",
|
||||
"message": "Your browser may not be allowed to record the screen on your computer.",
|
||||
"macInstructions": "Go to your",
|
||||
"macSystemPreferences": "System Preferences",
|
||||
"helpLinkText": "To learn more, see",
|
||||
"helpLinkLabel": "Presentation issue",
|
||||
"closeButton": "Dismiss",
|
||||
"newTab": "New window"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
@@ -177,6 +188,7 @@
|
||||
"access": {
|
||||
"title": "Room access",
|
||||
"description": "These settings will also apply to future occurrences of this meeting.",
|
||||
"type": "Meeting access types",
|
||||
"levels": {
|
||||
"public": {
|
||||
"label": "Open",
|
||||
|
||||
@@ -61,6 +61,17 @@
|
||||
"createRoom": {
|
||||
"heading": "Authentification requise",
|
||||
"body": "Cette réunion n'a pas encore été créée. Veuillez vous authentifier pour la créer ou attendre qu'un utilisateur authentifié le fasse."
|
||||
},
|
||||
"screenShare": {
|
||||
"title": "Impossible de partager votre écran",
|
||||
"ariaLabel": "Impossible de partager votre écran",
|
||||
"message": "Il se peut que votre navigateur ne soit pas autorisé à enregistrer l'écran sur votre ordinateur.",
|
||||
"macInstructions": "Accèdez à vos",
|
||||
"macSystemPreferences": "Préférences système",
|
||||
"helpLinkText": "Pour en savoir plus, consulter",
|
||||
"helpLinkLabel": "Problème de présentation",
|
||||
"closeButton": "Ignorer",
|
||||
"newTab": "Nouvelle fenêtre"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
@@ -176,11 +187,12 @@
|
||||
"description": "Ces paramètres organisateur vous permettent de garder le contrôle de votre réunion. Seuls les organisateurs peuvent accéder à ces commandes.",
|
||||
"access": {
|
||||
"title": "Accès à la réunion",
|
||||
"description": "Ces paramètres s'appliqueront également aux futures occurences de cette réunion.",
|
||||
"description": "Ces paramètres s'appliqueront également aux futures occurrences de cette réunion.",
|
||||
"type": "Type d'accès à la réunion",
|
||||
"levels": {
|
||||
"public": {
|
||||
"label": "Ouvrir",
|
||||
"description": "Persone n'a à demander pour rejoindre la réunion."
|
||||
"description": "Tout le monde peut rejoindre la réunion sans autorisation."
|
||||
},
|
||||
"trusted": {
|
||||
"label": "Ouvrir aux personnes de confiance",
|
||||
|
||||
@@ -34,7 +34,19 @@
|
||||
"usernameEmpty": "Uw naam kan niet leeg zijn"
|
||||
},
|
||||
"cameraDisabled": "Camera is uitgeschakeld.",
|
||||
"cameraStarting": "Camera wordt ingeschakeld."
|
||||
"cameraStarting": "Camera wordt ingeschakeld.",
|
||||
"waiting": {
|
||||
"title": "Verzoek tot deelname...",
|
||||
"body": "U kunt deelnemen aan dit gesprek wanneer iemand u toestemming geeft"
|
||||
},
|
||||
"denied": {
|
||||
"title": "U kunt niet deelnemen aan dit gesprek",
|
||||
"body": "Uw verzoek tot deelname is geweigerd."
|
||||
},
|
||||
"timeoutInvite": {
|
||||
"title": "U kunt niet deelnemen aan dit gesprek",
|
||||
"body": "Niemand heeft gereageerd op uw verzoek om deel te nemen aan het gesprek"
|
||||
}
|
||||
},
|
||||
"leaveRoomPrompt": "Dat zal u de vergadering doen verlaten.",
|
||||
"shareDialog": {
|
||||
@@ -49,6 +61,17 @@
|
||||
"createRoom": {
|
||||
"heading": "Verificatie vereist",
|
||||
"body": "Deze ruimte is nog niet gemaakt. Logt u alstublieft in om hem aan te maken, of wacht tot een ingelogde gebruiker dat doet."
|
||||
},
|
||||
"screenShare": {
|
||||
"title": "Kan uw scherm niet delen",
|
||||
"ariaLabel": "Kan uw scherm niet delen",
|
||||
"message": "Het is mogelijk dat uw browser geen toestemming heeft om het scherm op uw computer op te nemen.",
|
||||
"macInstructions": "Ga naar uw",
|
||||
"macSystemPreferences": "Systeemvoorkeuren",
|
||||
"helpLinkText": "Meer informatie, zie",
|
||||
"helpLinkLabel": "Presentatieprobleem",
|
||||
"closeButton": "Negeren",
|
||||
"newTab": "Nieuw venster"
|
||||
}
|
||||
},
|
||||
"controls": {
|
||||
@@ -84,6 +107,10 @@
|
||||
"open": "Verberg AI-assistent",
|
||||
"closed": "Toon AI-assistant"
|
||||
},
|
||||
"admin": {
|
||||
"open": "Verberg beheerder",
|
||||
"closed": "Toon beheerder"
|
||||
},
|
||||
"support": "Ondersteuning",
|
||||
"moreOptions": "Meer opties",
|
||||
"reactions": {
|
||||
@@ -129,13 +156,15 @@
|
||||
"participants": "Deelnemers",
|
||||
"effects": "Effecten",
|
||||
"chat": "Berichten in de chat",
|
||||
"transcript": "AI-assistent"
|
||||
"transcript": "AI-assistent",
|
||||
"admin": "Beheerdersbediening"
|
||||
},
|
||||
"content": {
|
||||
"participants": "deelnemers",
|
||||
"effects": "effecten",
|
||||
"chat": "berichten",
|
||||
"transcript": "AI-assistent"
|
||||
"transcript": "AI-assistent",
|
||||
"admin": "Beheerdersbediening"
|
||||
},
|
||||
"closeButton": "Verberg {{content}}"
|
||||
},
|
||||
@@ -154,6 +183,28 @@
|
||||
"button": "Stop met opname"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"description": "Deze organisatorinstellingen geven u controle over uw vergadering. Alleen organisatoren hebben toegang tot deze bedieningselementen.",
|
||||
"access": {
|
||||
"title": "Toegang tot vergadering",
|
||||
"description": "Deze instellingen zijn ook van toepassing op toekomstige sessies van deze vergadering.",
|
||||
"type": "Vergaderings toegangstypen",
|
||||
"levels": {
|
||||
"public": {
|
||||
"label": "Open",
|
||||
"description": "Niemand hoeft toestemming te vragen om deel te nemen aan de vergadering."
|
||||
},
|
||||
"trusted": {
|
||||
"label": "Open voor vertrouwde personen",
|
||||
"description": "Geauthenticeerde personen hoeven geen toestemming te vragen om deel te nemen aan de vergadering."
|
||||
},
|
||||
"restricted": {
|
||||
"label": "Beperkt",
|
||||
"description": "Personen die niet zijn uitgenodigd voor de vergadering moeten vragen om deel te nemen."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rating": {
|
||||
"submit": "Indienen",
|
||||
"question": "Wat vindt u van de kwaliteit van het gesprek?",
|
||||
@@ -172,6 +223,12 @@
|
||||
"heading": "Bedankt voor uw inzending",
|
||||
"body": "Ons productteam neemt de tijd om uw feedback zorgvuldig te bekijken. We zullen zo snel mogelijk antwoorden."
|
||||
},
|
||||
"authenticationMessage": {
|
||||
"heading": "Hoe kunnen we contact met u opnemen?",
|
||||
"placeholder": "Uw e-mail",
|
||||
"submit": "Verzenden",
|
||||
"ignore": "Negeren"
|
||||
},
|
||||
"participants": {
|
||||
"subheading": "In de ruimte",
|
||||
"you": "U",
|
||||
@@ -190,7 +247,20 @@
|
||||
},
|
||||
"raisedHands": "Opgestoken handen",
|
||||
"lowerParticipantHand": "Laat {{name}}'s hand zakken",
|
||||
"lowerParticipantsHand": "Laat alle handen zakken"
|
||||
"lowerParticipantsHand": "Laat alle handen zakken",
|
||||
"waiting": {
|
||||
"title": "Wachtkamer",
|
||||
"accept": {
|
||||
"button": "Accepteren",
|
||||
"label": "{{name}} toelaten tot de vergadering",
|
||||
"all": "Alles accepteren"
|
||||
},
|
||||
"deny": {
|
||||
"button": "Weigeren",
|
||||
"label": "{{name}} weigeren voor de vergadering",
|
||||
"all": "Alles weigeren"
|
||||
}
|
||||
}
|
||||
},
|
||||
"recording": {
|
||||
"label": "Opnemen"
|
||||
|
||||
@@ -75,7 +75,13 @@ export const A = ({
|
||||
return (
|
||||
<Link
|
||||
{...props}
|
||||
className={link({ size, externalIcon, underline, footer, color })}
|
||||
className={link({
|
||||
size,
|
||||
externalIcon,
|
||||
underline,
|
||||
footer,
|
||||
color,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const FieldWrapper = styled('div', {
|
||||
const StyledLabel = styled(Label, {
|
||||
base: {
|
||||
display: 'block',
|
||||
fontSize: '0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
variants: {
|
||||
center: {
|
||||
@@ -218,8 +218,10 @@ export const Field = <T extends object>({
|
||||
{item.description && (
|
||||
<Text
|
||||
variant="note"
|
||||
wrap={'pretty'}
|
||||
className={css({
|
||||
textStyle: 'sm',
|
||||
marginBottom: '0.5rem',
|
||||
})}
|
||||
>
|
||||
{item.description}
|
||||
|
||||
@@ -30,6 +30,46 @@
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Marianne';
|
||||
src: url('/fonts/Marianne-Regular-subset.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Marianne';
|
||||
src: url('/fonts/Marianne-Regular_Italic-subset.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Marianne';
|
||||
src: url('/fonts/Marianne-Medium-subset.woff2') format('woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Marianne';
|
||||
src: url('/fonts/Marianne-Bold-subset.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Marianne';
|
||||
src: url('/fonts/Marianne-ExtraBold-subset.woff2') format('woff2');
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/*
|
||||
* to reduce CLS
|
||||
* values taken from https://github.com/khempenius/font-fallbacks-dataset/blob/main/font-metric-overrides.csv#L2979
|
||||
|
||||
@@ -10,14 +10,14 @@ dependencies = [
|
||||
"celery==5.4.0",
|
||||
"redis==5.2.1",
|
||||
"minio==7.2.15",
|
||||
"openai==1.65.2",
|
||||
"openai==1.68.2",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk[fastapi, celery]==2.22.0",
|
||||
"sentry-sdk[fastapi, celery]==2.24.1",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"ruff==0.9.9",
|
||||
"ruff==0.11.2",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
Reference in New Issue
Block a user