Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5838ae8f1 | |||
| bece79f47b | |||
| a499331960 | |||
| d50d167d0a | |||
| e4c7bc0826 | |||
| b5244a5ec0 | |||
| 8cc2cc83c6 | |||
| e591d09b00 | |||
| eeb4dae12d | |||
| 271389d459 | |||
| fe6eefa1f0 | |||
| a276517278 | |||
| 9e9b9015f4 | |||
| 3f0378aada | |||
| 90c88a8bd3 | |||
| 1103902c12 | |||
| 812016d09c | |||
| cadc5c0034 | |||
| b083d837f8 | |||
| db8445f4ab | |||
| 0e5c2be445 | |||
| 0131a65509 | |||
| fb3727e73e | |||
| 3391165e4b | |||
| 0be94aa572 | |||
| b309f91095 | |||
| c50a749293 | |||
| cd9e7c8a1f | |||
| 9d0767ccfe | |||
| 021e52d9eb | |||
| 607a5fc99d | |||
| 756be17cc7 | |||
| 00fa7beebd | |||
| b9d13de591 | |||
| 7896890ffc | |||
| a2043abb51 | |||
| 64bb1b3bb5 | |||
| a1a56402d1 | |||
| 582d3774a4 | |||
| fab08cfaf8 | |||
| 550c48f29e | |||
| 0c64580abf | |||
| 0ad9b7e233 | |||
| b9bcc45cce | |||
| bcd285e368 | |||
| c0ad98eb34 | |||
| 178278235e | |||
| 11664956c7 | |||
| f020979188 | |||
| da5c8d6773 | |||
| 31051cd6c4 | |||
| c51058e6ac | |||
| 3ba913bb21 | |||
| 95116f70e8 | |||
| 5b282b62e7 | |||
| 5e74fce6e2 | |||
| 96b279b350 | |||
| fc4ad562ae | |||
| 480a8df31a |
@@ -0,0 +1,22 @@
|
||||
name: Helmfile lint
|
||||
run-name: Helmfile lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
helmfile-lint:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/helmfile/helmfile:latest
|
||||
steps:
|
||||
-
|
||||
uses: numerique-gouv/action-helmfile-lint@main
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
age-key: ${{ secrets.SOPS_PRIVATE }}
|
||||
private-key: ${{ secrets.PRIVATE_KEY }}
|
||||
helmfile-src: "src/helm"
|
||||
repositories: "meet,secrets"
|
||||
@@ -8,3 +8,4 @@ creation_rules:
|
||||
- age1tl80n23wq6zxegupwn70ew0yp225ua5v4dk800x7g2w6pvlxz46qk592pa #marie
|
||||
- age1qy04neuzwpasmvljqrcvhwnf0kz5cpyteze38c8avp0czewskasszv9pyw #argocd
|
||||
- age18fgn6j2vwwswqcpv9xpcehq8mrf9zs2sglwkamp3tzwx8d9jq9jsrskrk9 #manuuu
|
||||
- age1hm2hsfgjezpsc3k0y5w5feq9t8vl3seq04qjhgt6ztd6403wfvpsgxu09m # github-repo
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
HELMFILE=src/helm/helmfile.yaml
|
||||
|
||||
environments=$(awk '/environments:/ {flag=1; next} flag && NF {print} !NF {flag=0}' "$HELMFILE" | grep -E '^[[:space:]]{2}[a-zA-Z]+' | sed 's/^[[:space:]]*//;s/:.*//')
|
||||
|
||||
for env in $environments; do
|
||||
echo "################### $env lint ###################"
|
||||
helmfile -e $env -f src/helm/helmfile.yaml lint || exit 1
|
||||
echo -e "\n"
|
||||
done
|
||||
+3
-1
@@ -13,7 +13,9 @@
|
||||
"enabled": false,
|
||||
"groupName": "ignored js dependencies",
|
||||
"matchManagers": ["npm"],
|
||||
"matchPackageNames": []
|
||||
"matchPackageNames": [
|
||||
"eslint"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
Submodule secrets updated: f5fbc16e6e...8ef9f4513a
@@ -5,7 +5,7 @@ from django.urls import include, path
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from core.api import viewsets
|
||||
from core.api import get_frontend_configuration, viewsets
|
||||
from core.authentication.urls import urlpatterns as oidc_urls
|
||||
|
||||
# - Main endpoints
|
||||
@@ -23,6 +23,7 @@ urlpatterns = [
|
||||
[
|
||||
*router.urls,
|
||||
*oidc_urls,
|
||||
path("config/", get_frontend_configuration, name="config"),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
||||
@@ -53,7 +53,6 @@ def generate_token(room: str, user, username: Optional[str] = None) -> str:
|
||||
room=room,
|
||||
room_join=True,
|
||||
room_admin=True,
|
||||
room_record=True,
|
||||
can_update_own_metadata=True,
|
||||
can_publish_sources=[
|
||||
"camera",
|
||||
|
||||
@@ -251,6 +251,19 @@ class Base(Configuration):
|
||||
"REDOC_DIST": "SIDECAR",
|
||||
}
|
||||
|
||||
# Frontend
|
||||
FRONTEND_CONFIGURATION = {
|
||||
"analytics": values.DictValue(
|
||||
{}, environ_name="FRONTEND_ANALYTICS", environ_prefix=None
|
||||
),
|
||||
"support": values.DictValue(
|
||||
{}, environ_name="FRONTEND_SUPPORT", environ_prefix=None
|
||||
),
|
||||
"silence_livekit_debug_logs": values.BooleanValue(
|
||||
False, environ_name="FRONTEND_SILENCE_LIVEKIT_DEBUG", environ_prefix=None
|
||||
),
|
||||
}
|
||||
|
||||
# Mail
|
||||
EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend")
|
||||
EMAIL_HOST = values.Value(None)
|
||||
@@ -258,6 +271,7 @@ class Base(Configuration):
|
||||
EMAIL_HOST_PASSWORD = values.Value(None)
|
||||
EMAIL_PORT = values.PositiveIntegerValue(None)
|
||||
EMAIL_USE_TLS = values.BooleanValue(False)
|
||||
EMAIL_USE_SSL = values.BooleanValue(False)
|
||||
EMAIL_FROM = values.Value("from@example.com")
|
||||
|
||||
AUTH_USER_MODEL = "core.User"
|
||||
|
||||
+11
-11
@@ -2,12 +2,12 @@
|
||||
# Meet package
|
||||
#
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
requires = ["setuptools >= 61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -25,7 +25,7 @@ license = { file = "LICENSE" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"boto3==1.35.10",
|
||||
"boto3==1.35.19",
|
||||
"Brotli==1.1.0",
|
||||
"celery[redis]==5.4.0",
|
||||
"django-configurations==2.5.1",
|
||||
@@ -36,11 +36,11 @@ dependencies = [
|
||||
"django-redis==5.4.0",
|
||||
"django-storages[s3]==1.14.4",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.1",
|
||||
"django==5.1.1",
|
||||
"djangorestframework==3.15.2",
|
||||
"drf_spectacular==0.27.2",
|
||||
"dockerflow==2024.4.2",
|
||||
"easy_thumbnails==2.9",
|
||||
"easy_thumbnails==2.10",
|
||||
"factory_boy==3.3.1",
|
||||
"freezegun==1.5.1",
|
||||
"gunicorn==23.0.0",
|
||||
@@ -48,11 +48,11 @@ dependencies = [
|
||||
"june-analytics-python==2.3.0",
|
||||
"markdown==3.7",
|
||||
"nested-multipart-parser==1.5.0",
|
||||
"psycopg[binary]==3.2.1",
|
||||
"psycopg[binary]==3.2.2",
|
||||
"PyJWT==2.9.0",
|
||||
"python-frontmatter==1.1.0",
|
||||
"requests==2.32.3",
|
||||
"sentry-sdk==2.13.0",
|
||||
"sentry-sdk==2.14.0",
|
||||
"url-normalize==1.4.3",
|
||||
"WeasyPrint>=60.2",
|
||||
"whitenoise==6.7.0",
|
||||
@@ -76,13 +76,13 @@ dev = [
|
||||
"pylint-django==2.5.5",
|
||||
"pylint==3.2.7",
|
||||
"pytest-cov==5.0.0",
|
||||
"pytest-django==4.8.0",
|
||||
"pytest==8.3.2",
|
||||
"pytest-django==4.9.0",
|
||||
"pytest==8.3.3",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-xdist==3.6.1",
|
||||
"responses==0.25.3",
|
||||
"ruff==0.6.3",
|
||||
"types-requests==2.32.0.20240712",
|
||||
"ruff==0.6.5",
|
||||
"types-requests==2.32.0.20240914",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/play-icon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Meet</title>
|
||||
<title>Visio</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Generated
+2357
-2725
File diff suppressed because it is too large
Load Diff
+30
-27
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
@@ -13,45 +13,48 @@
|
||||
"check": "prettier --check ./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.3.3",
|
||||
"@livekit/components-styles": "1.0.12",
|
||||
"@pandacss/preset-panda": "0.41.0",
|
||||
"@livekit/components-react": "2.6.0",
|
||||
"@livekit/components-styles": "1.1.2",
|
||||
"@livekit/track-processors": "0.3.2",
|
||||
"@pandacss/preset-panda": "0.46.1",
|
||||
"@react-aria/toast": "3.0.0-beta.15",
|
||||
"@remixicon/react": "4.2.0",
|
||||
"@tanstack/react-query": "5.49.2",
|
||||
"@tanstack/react-query": "5.56.2",
|
||||
"crisp-sdk-web": "1.0.25",
|
||||
"hoofd": "1.7.1",
|
||||
"i18next": "23.12.1",
|
||||
"i18next": "23.15.1",
|
||||
"i18next-browser-languagedetector": "8.0.0",
|
||||
"i18next-parser": "9.0.0",
|
||||
"i18next-parser": "9.0.2",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"livekit-client": "2.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-aria-components": "1.2.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "14.1.3",
|
||||
"livekit-client": "2.5.3",
|
||||
"posthog-js": "1.164.1",
|
||||
"react": "18.3.1",
|
||||
"react-aria-components": "1.3.3",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "15.0.2",
|
||||
"use-sound": "4.0.3",
|
||||
"valtio": "1.13.2",
|
||||
"wouter": "3.3.0"
|
||||
"valtio": "2.0.0",
|
||||
"wouter": "3.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pandacss/dev": "0.41.0",
|
||||
"@tanstack/eslint-plugin-query": "5.49.1",
|
||||
"@tanstack/react-query-devtools": "5.49.2",
|
||||
"@types/node": "20.14.9",
|
||||
"@types/react": "18.3.3",
|
||||
"@pandacss/dev": "0.46.1",
|
||||
"@tanstack/eslint-plugin-query": "5.57.1",
|
||||
"@tanstack/react-query-devtools": "5.56.2",
|
||||
"@types/node": "20.16.6",
|
||||
"@types/react": "18.3.8",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.13.1",
|
||||
"@typescript-eslint/parser": "7.13.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.7.0",
|
||||
"@typescript-eslint/parser": "8.7.0",
|
||||
"@vitejs/plugin-react": "4.3.1",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-jsx-a11y": "6.9.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.0",
|
||||
"eslint-plugin-react-hooks": "4.6.2",
|
||||
"eslint-plugin-react-refresh": "0.4.7",
|
||||
"postcss": "8.4.39",
|
||||
"eslint-plugin-react-refresh": "0.4.12",
|
||||
"postcss": "8.4.47",
|
||||
"prettier": "3.3.3",
|
||||
"typescript": "5.5.2",
|
||||
"vite": "5.3.1",
|
||||
"vite-tsconfig-paths": "4.3.2"
|
||||
"typescript": "5.6.2",
|
||||
"vite": "5.4.5",
|
||||
"vite-tsconfig-paths": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,15 @@ import { Layout } from './layout/Layout'
|
||||
import { NotFoundScreen } from './components/NotFoundScreen'
|
||||
import { routes } from './routes'
|
||||
import './i18n/init'
|
||||
import { silenceLiveKitLogs } from '@/utils/livekit.ts'
|
||||
import { queryClient } from '@/api/queryClient'
|
||||
import { AppInitialization } from '@/components/AppInitialization'
|
||||
|
||||
function App() {
|
||||
const { i18n } = useTranslation()
|
||||
useLang(i18n.language)
|
||||
|
||||
const isProduction = import.meta.env.PROD
|
||||
silenceLiveKitLogs(isProduction)
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppInitialization />
|
||||
<Suspense fallback={null}>
|
||||
<I18nProvider locale={i18n.language}>
|
||||
<Layout>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const keys = {
|
||||
user: 'user',
|
||||
room: 'room',
|
||||
config: 'config',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { fetchApi } from './fetchApi'
|
||||
import { keys } from './queryKeys'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
export interface ApiConfig {
|
||||
analytics?: {
|
||||
id: string
|
||||
host: string
|
||||
}
|
||||
support?: {
|
||||
id: string
|
||||
}
|
||||
silence_livekit_debug_logs?: boolean
|
||||
}
|
||||
|
||||
const fetchConfig = (): Promise<ApiConfig> => {
|
||||
return fetchApi<ApiConfig>(`config/`)
|
||||
}
|
||||
|
||||
export const useConfig = () => {
|
||||
return useQuery({
|
||||
queryKey: [keys.config],
|
||||
queryFn: fetchConfig,
|
||||
staleTime: Infinity,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { silenceLiveKitLogs } from '@/utils/livekit'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
import { useAnalytics } from '@/features/analytics/hooks/useAnalytics'
|
||||
import { useSupport } from '@/features/support/hooks/useSupport'
|
||||
|
||||
export const AppInitialization = () => {
|
||||
const { data } = useConfig()
|
||||
|
||||
const {
|
||||
analytics = {},
|
||||
support = {},
|
||||
silence_livekit_debug_logs = false,
|
||||
} = data || {}
|
||||
|
||||
useAnalytics(analytics)
|
||||
useSupport(support)
|
||||
silenceLiveKitLogs(silence_livekit_debug_logs)
|
||||
|
||||
return null
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,39 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useLocation } from 'wouter'
|
||||
import posthog from 'posthog-js'
|
||||
import { ApiUser } from '@/features/auth/api/ApiUser'
|
||||
|
||||
export const startAnalyticsSession = (data: ApiUser) => {
|
||||
if (posthog._isIdentified()) return
|
||||
const { id, email } = data
|
||||
posthog.identify(id, { email })
|
||||
}
|
||||
|
||||
export const terminateAnalyticsSession = () => {
|
||||
if (!posthog._isIdentified()) return
|
||||
posthog.reset()
|
||||
}
|
||||
|
||||
export type useAnalyticsProps = {
|
||||
id?: string
|
||||
host?: string
|
||||
}
|
||||
|
||||
export const useAnalytics = ({ id, host }: useAnalyticsProps) => {
|
||||
const [location] = useLocation()
|
||||
useEffect(() => {
|
||||
if (!id || !host) return
|
||||
if (posthog.__loaded) return
|
||||
posthog.init(id, {
|
||||
api_host: host,
|
||||
person_profiles: 'always',
|
||||
})
|
||||
}, [id, host])
|
||||
|
||||
// From PostHog tutorial on PageView tracking in a Single Page Application (SPA) context.
|
||||
useEffect(() => {
|
||||
posthog.capture('$pageview')
|
||||
}, [location])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export const fetchUser = (): Promise<ApiUser | false> => {
|
||||
// make sure to not resolve the promise while trying to silent login
|
||||
// so that consumers of fetchUser don't think the work already ended
|
||||
if (canAttemptSilentLogin()) {
|
||||
attemptSilentLogin(3600)
|
||||
attemptSilentLogin(300)
|
||||
} else {
|
||||
resolve(false)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { fetchUser } from './fetchUser'
|
||||
import { type ApiUser } from './ApiUser'
|
||||
import { useEffect } from 'react'
|
||||
import { startAnalyticsSession } from '@/features/analytics/hooks/useAnalytics'
|
||||
import { initializeSupportSession } from '@/features/support/hooks/useSupport'
|
||||
|
||||
/**
|
||||
* returns info about currently logged in user
|
||||
* returns info about currently logged-in user
|
||||
*
|
||||
* `isLoggedIn` is undefined while query is loading and true/false when it's done
|
||||
*/
|
||||
@@ -12,9 +15,16 @@ export const useUser = () => {
|
||||
const query = useQuery({
|
||||
queryKey: [keys.user],
|
||||
queryFn: fetchUser,
|
||||
staleTime: 1000 * 60 * 60, // 1 hour
|
||||
staleTime: Infinity,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (query?.data) {
|
||||
startAnalyticsSession(query.data)
|
||||
initializeSupportSession(query.data)
|
||||
}
|
||||
}, [query.data])
|
||||
|
||||
const isLoggedIn =
|
||||
query.status === 'success' ? query.data !== false : undefined
|
||||
const isLoggedOut = isLoggedIn === false
|
||||
|
||||
@@ -23,7 +23,7 @@ export const JoinMeetingDialog = () => {
|
||||
name="roomId"
|
||||
label={t('joinInputLabel')}
|
||||
description={t('joinInputExample', {
|
||||
example: 'https://meet.numerique.gouv.fr/azer-tyu-qsdf',
|
||||
example: 'https://visio.numerique.gouv.fr/azer-tyu-qsdf',
|
||||
})}
|
||||
validate={(value) => {
|
||||
return !isRoomValid(value.trim()) ? (
|
||||
|
||||
@@ -44,6 +44,7 @@ export const LaterMeetingDialog = ({
|
||||
setIsCopied(true)
|
||||
}}
|
||||
onHoverChange={setIsHovered}
|
||||
data-attr="later-dialog-copy"
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DialogTrigger } from 'react-aria-components'
|
||||
import { DialogTrigger, MenuItem, Menu as RACMenu } from 'react-aria-components'
|
||||
import { Button, Menu, Text } from '@/primitives'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
import { Centered } from '@/layout/Centered'
|
||||
import { generateRoomId } from '@/features/rooms'
|
||||
import { authUrl, useUser, UserAware } from '@/features/auth'
|
||||
import { useUser, UserAware } from '@/features/auth'
|
||||
import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
||||
import { ProConnectButton } from '@/components/ProConnectButton'
|
||||
import { useCreateRoom } from '@/features/rooms'
|
||||
import { usePersistentUserChoices } from '@livekit/components-react'
|
||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
||||
import { RiAddLine, RiLink } from '@remixicon/react'
|
||||
import { MenuItem, Menu as RACMenu } from 'react-aria-components'
|
||||
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
|
||||
import { useState } from 'react'
|
||||
|
||||
@@ -42,10 +42,12 @@ export const Home = () => {
|
||||
{t('loginToCreateMeeting')}
|
||||
</Text>
|
||||
)}
|
||||
<HStack gap="gutter">
|
||||
<HStack gap="gutter" alignItems="start">
|
||||
{isLoggedIn ? (
|
||||
<Menu>
|
||||
<Button variant="primary">{t('createMeeting')}</Button>
|
||||
<Button variant="primary" data-attr="create-meeting">
|
||||
{t('createMeeting')}
|
||||
</Button>
|
||||
<RACMenu>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
@@ -57,6 +59,7 @@ export const Home = () => {
|
||||
})
|
||||
)
|
||||
}}
|
||||
data-attr="create-option-instant"
|
||||
>
|
||||
<RiAddLine size={18} />
|
||||
{t('createMenu.instantOption')}
|
||||
@@ -69,6 +72,7 @@ export const Home = () => {
|
||||
setLaterRoomId(data.slug)
|
||||
)
|
||||
}}
|
||||
data-attr="create-option-later"
|
||||
>
|
||||
<RiLink size={18} />
|
||||
{t('createMenu.laterOption')}
|
||||
@@ -76,12 +80,16 @@ export const Home = () => {
|
||||
</RACMenu>
|
||||
</Menu>
|
||||
) : (
|
||||
<Button variant="primary" href={authUrl()}>
|
||||
{t('login', { ns: 'global' })}
|
||||
</Button>
|
||||
<ProConnectButton />
|
||||
)}
|
||||
<DialogTrigger>
|
||||
<Button variant="primary" outline>
|
||||
<Button
|
||||
variant="primary"
|
||||
outline
|
||||
style={{
|
||||
height: !isLoggedIn ? '56px' : undefined, // Temporary, Align with ProConnect Button fixed height
|
||||
}}
|
||||
>
|
||||
{t('joinMeeting')}
|
||||
</Button>
|
||||
<JoinMeetingDialog />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { Participant, RemoteParticipant, RoomEvent } from 'livekit-client'
|
||||
import { Participant, RoomEvent } from 'livekit-client'
|
||||
import { ToastProvider, toastQueue } from './components/ToastProvider'
|
||||
import { NotificationType } from './NotificationType'
|
||||
import { Div } from '@/primitives'
|
||||
@@ -50,31 +50,32 @@ export const MainNotificationToast = () => {
|
||||
}
|
||||
}, [room])
|
||||
|
||||
// fixme - close all related toasters when hands are lowered remotely
|
||||
useEffect(() => {
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
const handleNotificationReceived = (
|
||||
payload: Uint8Array,
|
||||
participant?: RemoteParticipant
|
||||
prevMetadataStr: string | undefined,
|
||||
participant: Participant
|
||||
) => {
|
||||
if (!participant) {
|
||||
return
|
||||
}
|
||||
if (isMobileBrowser()) {
|
||||
return
|
||||
}
|
||||
const notification = decoder.decode(payload)
|
||||
if (!participant) return
|
||||
if (isMobileBrowser()) return
|
||||
if (participant.isLocal) return
|
||||
|
||||
const prevMetadata = JSON.parse(prevMetadataStr || '{}')
|
||||
const metadata = JSON.parse(participant.metadata || '{}')
|
||||
|
||||
if (prevMetadata.raised == metadata.raised) return
|
||||
|
||||
const existingToast = toastQueue.visibleToasts.find(
|
||||
(toast) =>
|
||||
toast.content.participant === participant &&
|
||||
toast.content.type === NotificationType.Raised
|
||||
)
|
||||
if (existingToast && notification === NotificationType.Lowered) {
|
||||
|
||||
if (existingToast && prevMetadata.raised && !metadata.raised) {
|
||||
toastQueue.close(existingToast.key)
|
||||
return
|
||||
}
|
||||
if (!existingToast && notification === NotificationType.Raised) {
|
||||
|
||||
if (!existingToast && !prevMetadata.raised && metadata.raised) {
|
||||
triggerNotificationSound(NotificationType.Raised)
|
||||
toastQueue.add(
|
||||
{
|
||||
@@ -86,13 +87,23 @@ export const MainNotificationToast = () => {
|
||||
}
|
||||
}
|
||||
|
||||
room.on(RoomEvent.DataReceived, handleNotificationReceived)
|
||||
room.on(RoomEvent.ParticipantMetadataChanged, handleNotificationReceived)
|
||||
|
||||
return () => {
|
||||
room.off(RoomEvent.DataReceived, handleNotificationReceived)
|
||||
room.off(RoomEvent.ParticipantMetadataChanged, handleNotificationReceived)
|
||||
}
|
||||
}, [room, triggerNotificationSound])
|
||||
|
||||
useEffect(() => {
|
||||
const closeAllToasts = () => {
|
||||
toastQueue.visibleToasts.forEach(({ key }) => toastQueue.close(key))
|
||||
}
|
||||
room.on(RoomEvent.Disconnected, closeAllToasts)
|
||||
return () => {
|
||||
room.off(RoomEvent.Disconnected, closeAllToasts)
|
||||
}
|
||||
}, [room])
|
||||
|
||||
return (
|
||||
<Div position="absolute" bottom={20} right={5} zIndex={1000}>
|
||||
<ToastProvider />
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { Room, RoomOptions } from 'livekit-client'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { queryClient } from '@/api/queryClient'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { Screen } from '@/layout/Screen'
|
||||
import { QueryAware } from '@/components/QueryAware'
|
||||
import { ErrorScreen } from '@/components/ErrorScreen'
|
||||
@@ -19,6 +18,7 @@ import { useCreateRoom } from '../api/createRoom'
|
||||
import { InviteDialog } from './InviteDialog'
|
||||
|
||||
import { VideoConference } from '../livekit/prefabs/VideoConference'
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
export const Conference = ({
|
||||
roomId,
|
||||
@@ -31,6 +31,9 @@ export const Conference = ({
|
||||
mode?: 'join' | 'create'
|
||||
initialRoomData?: ApiRoom
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
posthog.capture('visit-room', { slug: roomId })
|
||||
}, [roomId])
|
||||
const fetchKey = [keys.room, roomId, userConfig.username]
|
||||
|
||||
const {
|
||||
@@ -79,25 +82,6 @@ export const Conference = ({
|
||||
|
||||
const [showInviteDialog, setShowInviteDialog] = useState(mode === 'create')
|
||||
|
||||
/**
|
||||
* checks for actual click on the leave button instead of
|
||||
* relying on LiveKitRoom onDisconnected because onDisconnected
|
||||
* triggers even on page reload, it's not a user "onLeave" event really.
|
||||
* Here we want to react to the user actually deciding to leave.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const checkOnLeaveClick = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (target.classList.contains('lk-disconnect-button')) {
|
||||
navigateTo('feedback')
|
||||
}
|
||||
}
|
||||
document.body.addEventListener('click', checkOnLeaveClick)
|
||||
return () => {
|
||||
document.body.removeEventListener('click', checkOnLeaveClick)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const { t } = useTranslation('rooms')
|
||||
if (isCreateError) {
|
||||
// this error screen should be replaced by a proper waiting room for anonymous user.
|
||||
|
||||
@@ -90,6 +90,7 @@ export const InviteDialog = ({
|
||||
setIsCopied(true)
|
||||
}}
|
||||
onHoverChange={setIsHovered}
|
||||
data-attr="share-dialog-copy"
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
|
||||
@@ -16,8 +16,8 @@ export const Join = ({
|
||||
<PreJoin
|
||||
persistUserChoices
|
||||
onSubmit={onSubmit}
|
||||
micLabel={t('join.micLabel')}
|
||||
camLabel={t('join.camlabel')}
|
||||
micLabel={t('join.audioinput.label')}
|
||||
camLabel={t('join.videoinput.label')}
|
||||
joinLabel={t('join.joinLabel')}
|
||||
userLabel={t('join.userLabel')}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,7 @@ export const useLowerHandParticipants = () => {
|
||||
)
|
||||
return Promise.all(promises)
|
||||
} catch (error) {
|
||||
console.error('An error occurred while lowering hands :', error)
|
||||
throw new Error('An error occurred while lowering hands.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { fetchServerApi } from './fetchServerApi'
|
||||
import { buildServerApiUrl } from './buildServerApiUrl'
|
||||
import { useRoomData } from '../hooks/useRoomData'
|
||||
import { useParams } from 'wouter'
|
||||
|
||||
export const useRecordRoom = () => {
|
||||
const data = useRoomData()
|
||||
const { roomId: roomSlug } = useParams()
|
||||
|
||||
const recordRoom = () => {
|
||||
if (!data || !data?.livekit) {
|
||||
throw new Error('Room data is not available')
|
||||
}
|
||||
if (!roomSlug) {
|
||||
throw new Error('Room ID is not available')
|
||||
}
|
||||
return fetchServerApi(
|
||||
buildServerApiUrl(
|
||||
data.livekit.url,
|
||||
'/twirp/livekit.Egress/StartRoomCompositeEgress'
|
||||
),
|
||||
data.livekit.token,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
room_name: data.livekit.room,
|
||||
audio_only: true,
|
||||
file_outputs: [
|
||||
{
|
||||
file_extension: 'ogg',
|
||||
filepath: `{room_name}_{time}_${roomSlug}`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const stopRecordingRoom = (egressId: string) => {
|
||||
if (!data || !data?.livekit) {
|
||||
throw new Error('Room data is not available')
|
||||
}
|
||||
return fetchServerApi(
|
||||
buildServerApiUrl(data.livekit.url, '/twirp/livekit.Egress/StopEgress'),
|
||||
data.livekit.token,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
egressId,
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return { recordRoom, stopRecordingRoom }
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useLocalParticipant } from '@livekit/components-react'
|
||||
import { LocalVideoTrack } from 'livekit-client'
|
||||
import { Text, P, ToggleButton, Div, H } from '@/primitives'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { HStack, styled, VStack } from '@/styled-system/jsx'
|
||||
import {
|
||||
BackgroundBlur,
|
||||
BackgroundOptions,
|
||||
ProcessorWrapper,
|
||||
BackgroundTransformer,
|
||||
} from '@livekit/track-processors'
|
||||
|
||||
const Information = styled('div', {
|
||||
base: {
|
||||
backgroundColor: 'orange.50',
|
||||
borderRadius: '4px',
|
||||
padding: '0.75rem 0.75rem',
|
||||
marginTop: '0.8rem',
|
||||
alignItems: 'start',
|
||||
},
|
||||
})
|
||||
|
||||
enum BlurRadius {
|
||||
NONE = 0,
|
||||
LIGHT = 5,
|
||||
NORMAL = 10,
|
||||
}
|
||||
|
||||
export const Effects = () => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
|
||||
const { isCameraEnabled, cameraTrack, localParticipant } =
|
||||
useLocalParticipant()
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const [processorPending, setProcessorPending] = useState(false)
|
||||
|
||||
const localCameraTrack = cameraTrack?.track as LocalVideoTrack
|
||||
|
||||
const getProcessor = () => {
|
||||
return localCameraTrack?.getProcessor() as ProcessorWrapper<BackgroundOptions>
|
||||
}
|
||||
|
||||
const getBlurRadius = (): BlurRadius => {
|
||||
const processor = getProcessor()
|
||||
return (
|
||||
(processor?.transformer as BackgroundTransformer)?.options?.blurRadius ||
|
||||
BlurRadius.NONE
|
||||
)
|
||||
}
|
||||
|
||||
const toggleBlur = async (blurRadius: number) => {
|
||||
if (!isCameraEnabled) await localParticipant.setCameraEnabled(true)
|
||||
if (!localCameraTrack) return
|
||||
|
||||
setProcessorPending(true)
|
||||
|
||||
const processor = getProcessor()
|
||||
const currentBlurRadius = getBlurRadius()
|
||||
|
||||
try {
|
||||
if (blurRadius == currentBlurRadius && processor) {
|
||||
await localCameraTrack.stopProcessor()
|
||||
} else if (!processor) {
|
||||
await localCameraTrack.setProcessor(BackgroundBlur(blurRadius))
|
||||
} else {
|
||||
await processor?.updateTransformerOptions({ blurRadius })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error applying blur:', error)
|
||||
} finally {
|
||||
setProcessorPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const videoElement = videoRef.current
|
||||
if (!videoElement) return
|
||||
|
||||
const attachVideoTrack = async () => localCameraTrack?.attach(videoElement)
|
||||
attachVideoTrack()
|
||||
|
||||
return () => {
|
||||
if (!videoElement) return
|
||||
localCameraTrack.detach(videoElement)
|
||||
}
|
||||
}, [localCameraTrack, isCameraEnabled])
|
||||
|
||||
const isSelected = (blurRadius: BlurRadius) => {
|
||||
return isCameraEnabled && getBlurRadius() == blurRadius
|
||||
}
|
||||
|
||||
const tooltipLabel = (blurRadius: BlurRadius) => {
|
||||
return t(`blur.${isSelected(blurRadius) ? 'clear' : 'apply'}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack padding="0 1.5rem">
|
||||
{localCameraTrack && isCameraEnabled ? (
|
||||
<video
|
||||
ref={videoRef}
|
||||
width="100%"
|
||||
muted
|
||||
style={{
|
||||
transform: 'rotateY(180deg)',
|
||||
minHeight: '175px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '174px',
|
||||
display: 'flex',
|
||||
backgroundColor: 'black',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<P
|
||||
style={{
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
textWrap: 'balance',
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
{t('activateCamera')}
|
||||
</P>
|
||||
</div>
|
||||
)}
|
||||
<Div
|
||||
alignItems={'left'}
|
||||
width={'100%'}
|
||||
style={{
|
||||
border: '1px solid #dadce0',
|
||||
borderRadius: '8px',
|
||||
margin: '0 .625rem',
|
||||
padding: '0.5rem 1rem',
|
||||
}}
|
||||
>
|
||||
<H
|
||||
lvl={3}
|
||||
style={{
|
||||
marginBottom: '0.4rem',
|
||||
}}
|
||||
>
|
||||
{t('heading')}
|
||||
</H>
|
||||
{ProcessorWrapper.isSupported ? (
|
||||
<HStack>
|
||||
<ToggleButton
|
||||
size={'sm'}
|
||||
legacyStyle
|
||||
aria-label={tooltipLabel(BlurRadius.LIGHT)}
|
||||
tooltip={tooltipLabel(BlurRadius.LIGHT)}
|
||||
isDisabled={processorPending}
|
||||
onPress={async () => await toggleBlur(BlurRadius.LIGHT)}
|
||||
isSelected={isSelected(BlurRadius.LIGHT)}
|
||||
>
|
||||
{t('blur.light')}
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
size={'sm'}
|
||||
legacyStyle
|
||||
aria-label={tooltipLabel(BlurRadius.NORMAL)}
|
||||
tooltip={tooltipLabel(BlurRadius.NORMAL)}
|
||||
isDisabled={processorPending}
|
||||
onPress={async () => await toggleBlur(BlurRadius.NORMAL)}
|
||||
isSelected={isSelected(BlurRadius.NORMAL)}
|
||||
>
|
||||
{t('blur.normal')}
|
||||
</ToggleButton>
|
||||
</HStack>
|
||||
) : (
|
||||
<Text variant="sm">{t('notAvailable')}</Text>
|
||||
)}
|
||||
<Information>
|
||||
<Text
|
||||
variant="sm"
|
||||
style={{
|
||||
textWrap: 'balance',
|
||||
}}
|
||||
>
|
||||
⚠︎ {t('experimental')}
|
||||
</Text>
|
||||
</Information>
|
||||
</Div>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { RoomEvent } from 'livekit-client'
|
||||
|
||||
export const RecordingIndicator = () => {
|
||||
const room = useRoomContext()
|
||||
const [isRecording, setIsRecording] = useState(room.isRecording)
|
||||
|
||||
useEffect(() => {
|
||||
const handleRecordingStatusChanges = (isRecording: boolean) => {
|
||||
setIsRecording(isRecording)
|
||||
}
|
||||
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanges)
|
||||
return () => {
|
||||
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanges)
|
||||
}
|
||||
}, [room])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
Room is recording: {isRecording ? 'yes' : 'no'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { layoutStore } from '@/stores/layout'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { Heading } from 'react-aria-components'
|
||||
import { text } from '@/primitives/Text'
|
||||
import { Box, Button, Div } from '@/primitives'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
||||
import { useWidgetInteraction } from '../hooks/useWidgetInteraction'
|
||||
import { ReactNode } from 'react'
|
||||
import { Effects } from './Effects'
|
||||
|
||||
type StyledSidePanelProps = {
|
||||
title: string
|
||||
children: ReactNode
|
||||
onClose: () => void
|
||||
closeButtonTooltip: string
|
||||
}
|
||||
|
||||
const StyledSidePanel = ({
|
||||
title,
|
||||
children,
|
||||
onClose,
|
||||
closeButtonTooltip,
|
||||
}: StyledSidePanelProps) => (
|
||||
<Box
|
||||
size="sm"
|
||||
minWidth="360px"
|
||||
className={css({
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
margin: '1.5rem 1.5rem 1.5rem 0',
|
||||
padding: 0,
|
||||
gap: 0,
|
||||
})}
|
||||
>
|
||||
<Heading
|
||||
slot="title"
|
||||
level={1}
|
||||
className={text({ variant: 'h2' })}
|
||||
style={{
|
||||
paddingLeft: '1.5rem',
|
||||
paddingTop: '1rem',
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Heading>
|
||||
<Div position="absolute" top="5" right="5">
|
||||
<Button
|
||||
invisible
|
||||
size="xs"
|
||||
onPress={onClose}
|
||||
aria-label={closeButtonTooltip}
|
||||
tooltip={closeButtonTooltip}
|
||||
>
|
||||
<RiCloseLine />
|
||||
</Button>
|
||||
</Div>
|
||||
<Div overflowY="scroll">{children}</Div>
|
||||
</Box>
|
||||
)
|
||||
|
||||
export const SidePanel = () => {
|
||||
const layoutSnap = useSnapshot(layoutStore)
|
||||
const sidePanel = layoutSnap.sidePanel
|
||||
|
||||
const { isParticipantsOpen, isEffectsOpen } = useWidgetInteraction()
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
|
||||
|
||||
if (!sidePanel) {
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledSidePanel
|
||||
title={t(`heading.${sidePanel}`)}
|
||||
onClose={() => (layoutStore.sidePanel = null)}
|
||||
closeButtonTooltip={t('closeButton', {
|
||||
content: t(`content.${sidePanel}`),
|
||||
})}
|
||||
>
|
||||
{isParticipantsOpen && <ParticipantsList />}
|
||||
{isEffectsOpen && <Effects />}
|
||||
</StyledSidePanel>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { css } from '@/styled-system/css'
|
||||
import { useWidgetInteraction } from '../../hooks/useWidgetInteraction'
|
||||
|
||||
export const ChatToggle = () => {
|
||||
const { t } = useTranslation('rooms')
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.chat' })
|
||||
|
||||
const { isChatOpen, unreadMessages, toggleChat } = useWidgetInteraction()
|
||||
const tooltipLabel = isChatOpen ? 'open' : 'closed'
|
||||
@@ -20,10 +20,11 @@ export const ChatToggle = () => {
|
||||
<ToggleButton
|
||||
square
|
||||
legacyStyle
|
||||
aria-label={t(`controls.chat.${tooltipLabel}`)}
|
||||
tooltip={t(`controls.chat.${tooltipLabel}`)}
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
isSelected={isChatOpen}
|
||||
onPress={() => toggleChat()}
|
||||
data-attr={`controls-chat-${tooltipLabel}`}
|
||||
>
|
||||
<RiChat1Line />
|
||||
</ToggleButton>
|
||||
|
||||
@@ -4,30 +4,16 @@ import { ToggleButton } from '@/primitives'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { useRoomContext } from '@livekit/components-react'
|
||||
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
|
||||
import { NotificationType } from '@/features/notifications/NotificationType'
|
||||
|
||||
export const HandToggle = () => {
|
||||
const { t } = useTranslation('rooms')
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.hand' })
|
||||
|
||||
const room = useRoomContext()
|
||||
const { isHandRaised, toggleRaisedHand } = useRaisedHand({
|
||||
participant: room.localParticipant,
|
||||
})
|
||||
|
||||
const label = isHandRaised
|
||||
? t('controls.hand.lower')
|
||||
: t('controls.hand.raise')
|
||||
|
||||
const notifyOtherParticipants = (isHandRaised: boolean) => {
|
||||
room.localParticipant.publishData(
|
||||
new TextEncoder().encode(
|
||||
!isHandRaised ? NotificationType.Raised : NotificationType.Lowered
|
||||
),
|
||||
{
|
||||
reliable: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
const tooltipLabel = isHandRaised ? 'lower' : 'raise'
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -39,13 +25,11 @@ export const HandToggle = () => {
|
||||
<ToggleButton
|
||||
square
|
||||
legacyStyle
|
||||
aria-label={label}
|
||||
tooltip={label}
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
isSelected={isHandRaised}
|
||||
onPress={() => {
|
||||
notifyOtherParticipants(isHandRaised)
|
||||
toggleRaisedHand()
|
||||
}}
|
||||
onPress={() => toggleRaisedHand()}
|
||||
data-attr={`controls-hand-${tooltipLabel}`}
|
||||
>
|
||||
<RiHand />
|
||||
</ToggleButton>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useConnectionState, useRoomContext } from '@livekit/components-react'
|
||||
import { Button } from '@/primitives'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { RiPhoneFill } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ConnectionState } from 'livekit-client'
|
||||
|
||||
export const LeaveButton = () => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls' })
|
||||
const room = useRoomContext()
|
||||
const connectionState = useConnectionState(room)
|
||||
return (
|
||||
<Button
|
||||
isDisabled={connectionState === ConnectionState.Disconnected}
|
||||
variant={'danger'}
|
||||
tooltip={t('leave')}
|
||||
aria-label={t('leave')}
|
||||
onPress={() => {
|
||||
room
|
||||
.disconnect(true)
|
||||
.catch((e) =>
|
||||
console.error('An error occurred while disconnecting:', e)
|
||||
)
|
||||
.finally(() => {
|
||||
navigateTo('feedback')
|
||||
})
|
||||
}}
|
||||
data-attr="controls-leave"
|
||||
>
|
||||
<RiPhoneFill
|
||||
style={{
|
||||
transform: 'rotate(135deg)',
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
+34
-30
@@ -1,14 +1,15 @@
|
||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
||||
import {
|
||||
RiAccountBoxLine,
|
||||
RiFeedbackLine,
|
||||
RiQuestionLine,
|
||||
RiSettings3Line,
|
||||
} from '@remixicon/react'
|
||||
import { MenuItem, Menu as RACMenu } from 'react-aria-components'
|
||||
import { MenuItem, Menu as RACMenu, Section } from 'react-aria-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
import { DialogState } from '@/features/rooms/livekit/components/controls/Options/OptionsButton'
|
||||
import { RecordingMenuItem } from './RecordingMenuItem.tsx'
|
||||
import { DialogState } from './OptionsButton'
|
||||
import { Separator } from '@/primitives/Separator'
|
||||
import { useWidgetInteraction } from '../../../hooks/useWidgetInteraction'
|
||||
|
||||
// @todo try refactoring it to use MenuList component
|
||||
export const OptionsMenuItems = ({
|
||||
@@ -16,8 +17,8 @@ export const OptionsMenuItems = ({
|
||||
}: {
|
||||
onOpenDialog: Dispatch<SetStateAction<DialogState>>
|
||||
}) => {
|
||||
const { t } = useTranslation('rooms')
|
||||
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
|
||||
const { toggleEffects } = useWidgetInteraction()
|
||||
return (
|
||||
<RACMenu
|
||||
style={{
|
||||
@@ -25,30 +26,33 @@ export const OptionsMenuItems = ({
|
||||
width: '300px',
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
href="https://tchap.gouv.fr/#/room/!aGImQayAgBLjSBycpm:agent.dinum.tchap.gouv.fr?via=agent.dinum.tchap.gouv.fr"
|
||||
target="_blank"
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
>
|
||||
<RiQuestionLine size={18} />
|
||||
{t('options.items.support')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
|
||||
target="_blank"
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
>
|
||||
<RiFeedbackLine size={18} />
|
||||
{t('options.items.feedbacks')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
onAction={() => onOpenDialog('settings')}
|
||||
>
|
||||
<RiSettings3Line size={18} />
|
||||
{t('options.items.settings')}
|
||||
</MenuItem>
|
||||
<RecordingMenuItem />
|
||||
<Section>
|
||||
<MenuItem
|
||||
onAction={() => toggleEffects()}
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
>
|
||||
<RiAccountBoxLine size={20} />
|
||||
{t('effects')}
|
||||
</MenuItem>
|
||||
</Section>
|
||||
<Separator />
|
||||
<Section>
|
||||
<MenuItem
|
||||
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
|
||||
target="_blank"
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
>
|
||||
<RiFeedbackLine size={20} />
|
||||
{t('feedbacks')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
onAction={() => onOpenDialog('settings')}
|
||||
>
|
||||
<RiSettings3Line size={20} />
|
||||
{t('settings')}
|
||||
</MenuItem>
|
||||
</Section>
|
||||
</RACMenu>
|
||||
)
|
||||
}
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import { MenuItem } from 'react-aria-components'
|
||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe.ts'
|
||||
import { RiPauseCircleLine, RiRecordCircleLine } from '@remixicon/react'
|
||||
import { useRecordRoom } from '@/features/rooms/livekit/api/recordRoom.ts'
|
||||
import { useState } from 'react'
|
||||
import { egressStore } from '@/stores/egress.tsx'
|
||||
import { useSnapshot } from 'valtio'
|
||||
|
||||
export const RecordingMenuItem = () => {
|
||||
const { recordRoom, stopRecordingRoom } = useRecordRoom()
|
||||
|
||||
const egressSnap = useSnapshot(egressStore)
|
||||
const egressId = egressSnap.egressId
|
||||
|
||||
const [isPending, setIsPending] = useState(false)
|
||||
|
||||
const handleAction = async () => {
|
||||
if (egressId) {
|
||||
setIsPending(true)
|
||||
const response = await stopRecordingRoom(egressId)
|
||||
console.log(response)
|
||||
egressStore.egressId = undefined
|
||||
setIsPending(false)
|
||||
} else {
|
||||
setIsPending(true)
|
||||
const response = await recordRoom()
|
||||
egressStore.egressId = response['egress_id'] as string
|
||||
setIsPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
isDisabled={isPending}
|
||||
className={menuItemRecipe({ icon: true })}
|
||||
onAction={handleAction}
|
||||
>
|
||||
{egressId ? (
|
||||
<>
|
||||
<RiPauseCircleLine size={18} />
|
||||
Stop recording room
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RiRecordCircleLine size={18} />
|
||||
Record room
|
||||
</>
|
||||
)}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
+1
@@ -73,6 +73,7 @@ export const HandRaisedListItem = ({
|
||||
size="sm"
|
||||
onPress={() => lowerHandParticipant(participant)}
|
||||
tooltip={t('participants.lowerParticipantHand', { name })}
|
||||
data-attr="participants-lower-hand"
|
||||
>
|
||||
<RiHand />
|
||||
</Button>
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ export const LowerAllHandsButton = ({
|
||||
fullWidth
|
||||
variant="text"
|
||||
onPress={() => lowerHandParticipants(participants)}
|
||||
data-attr="participants-lower-hands"
|
||||
>
|
||||
{t('participants.lowerParticipantsHand')}
|
||||
</Button>
|
||||
|
||||
+1
@@ -78,6 +78,7 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
? muteParticipant(participant)
|
||||
: setIsAlertOpen(true)
|
||||
}
|
||||
data-attr="participants-mute"
|
||||
>
|
||||
{isMuted ? (
|
||||
<RiMicOffFill color={'gray'} />
|
||||
|
||||
+45
-80
@@ -1,27 +1,27 @@
|
||||
import { css } from '@/styled-system/css'
|
||||
import { allParticipantRoomEvents } from '@livekit/components-core'
|
||||
import { useParticipants } from '@livekit/components-react'
|
||||
|
||||
import { Heading } from 'react-aria-components'
|
||||
import { Box, Button, Div, H } from '@/primitives'
|
||||
import { text } from '@/primitives/Text'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { participantsStore } from '@/stores/participants'
|
||||
import { Div, H } from '@/primitives'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { allParticipantRoomEvents } from '@/features/rooms/livekit/constants/events'
|
||||
import { ParticipantListItem } from '@/features/rooms/livekit/components/controls/Participants/ParticipantListItem'
|
||||
import { ParticipantsCollapsableList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsCollapsableList'
|
||||
import { HandRaisedListItem } from '@/features/rooms/livekit/components/controls/Participants/HandRaisedListItem'
|
||||
import { LowerAllHandsButton } from '@/features/rooms/livekit/components/controls/Participants/LowerAllHandsButton'
|
||||
import { ParticipantListItem } from '../../controls/Participants/ParticipantListItem'
|
||||
import { ParticipantsCollapsableList } from '../../controls/Participants/ParticipantsCollapsableList'
|
||||
import { HandRaisedListItem } from '../../controls/Participants/HandRaisedListItem'
|
||||
import { LowerAllHandsButton } from '../../controls/Participants/LowerAllHandsButton'
|
||||
import { RoomEvent } from 'livekit-client'
|
||||
|
||||
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
|
||||
export const ParticipantsList = () => {
|
||||
const { t } = useTranslation('rooms')
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'participants' })
|
||||
|
||||
// Preferred using the 'useParticipants' hook rather than the separate remote and local hooks,
|
||||
// because the 'useLocalParticipant' hook does not update the participant's information when their
|
||||
// metadata/name changes. The LiveKit team has marked this as a TODO item in the code.
|
||||
const participants = useParticipants({
|
||||
updateOnlyOn: allParticipantRoomEvents,
|
||||
updateOnlyOn: [
|
||||
RoomEvent.ParticipantNameChanged,
|
||||
...allParticipantRoomEvents,
|
||||
],
|
||||
})
|
||||
|
||||
const sortedRemoteParticipants = participants
|
||||
@@ -44,75 +44,40 @@ export const ParticipantsList = () => {
|
||||
|
||||
// TODO - extract inline styling in a centralized styling file, and avoid magic numbers
|
||||
return (
|
||||
<Box
|
||||
size="sm"
|
||||
minWidth="360px"
|
||||
className={css({
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
margin: '1.5rem 1.5rem 1.5rem 0',
|
||||
padding: 0,
|
||||
gap: 0,
|
||||
})}
|
||||
>
|
||||
<Heading
|
||||
slot="title"
|
||||
level={1}
|
||||
className={text({ variant: 'h2' })}
|
||||
style={{
|
||||
paddingLeft: '1.5rem',
|
||||
paddingTop: '1rem',
|
||||
}}
|
||||
<>
|
||||
<H
|
||||
lvl={2}
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: '#5f6368',
|
||||
padding: '0 1.5rem',
|
||||
marginBottom: '0.83em',
|
||||
})}
|
||||
>
|
||||
{t('participants.heading')}
|
||||
</Heading>
|
||||
<Div position="absolute" top="5" right="5">
|
||||
<Button
|
||||
invisible
|
||||
size="xs"
|
||||
onPress={() => (participantsStore.showParticipants = false)}
|
||||
aria-label={t('participants.closeButton')}
|
||||
tooltip={t('participants.closeButton')}
|
||||
>
|
||||
<RiCloseLine />
|
||||
</Button>
|
||||
</Div>
|
||||
<Div overflowY="scroll">
|
||||
<H
|
||||
lvl={2}
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: '#5f6368',
|
||||
padding: '0 1.5rem',
|
||||
marginBottom: '0.83em',
|
||||
})}
|
||||
>
|
||||
{t('participants.subheading').toUpperCase()}
|
||||
</H>
|
||||
{raisedHandParticipants.length > 0 && (
|
||||
<Div marginBottom=".9375rem">
|
||||
<ParticipantsCollapsableList
|
||||
heading={t('participants.raisedHands')}
|
||||
participants={raisedHandParticipants}
|
||||
renderParticipant={(participant) => (
|
||||
<HandRaisedListItem participant={participant} />
|
||||
)}
|
||||
action={() => (
|
||||
<LowerAllHandsButton participants={raisedHandParticipants} />
|
||||
)}
|
||||
/>
|
||||
</Div>
|
||||
{t('subheading').toUpperCase()}
|
||||
</H>
|
||||
{raisedHandParticipants.length > 0 && (
|
||||
<Div marginBottom=".9375rem">
|
||||
<ParticipantsCollapsableList
|
||||
heading={t('raisedHands')}
|
||||
participants={raisedHandParticipants}
|
||||
renderParticipant={(participant) => (
|
||||
<HandRaisedListItem participant={participant} />
|
||||
)}
|
||||
action={() => (
|
||||
<LowerAllHandsButton participants={raisedHandParticipants} />
|
||||
)}
|
||||
/>
|
||||
</Div>
|
||||
)}
|
||||
<ParticipantsCollapsableList
|
||||
heading={t('contributors')}
|
||||
participants={sortedParticipants}
|
||||
renderParticipant={(participant) => (
|
||||
<ParticipantListItem participant={participant} />
|
||||
)}
|
||||
<ParticipantsCollapsableList
|
||||
heading={t('participants.contributors')}
|
||||
participants={sortedParticipants}
|
||||
renderParticipant={(participant) => (
|
||||
<ParticipantListItem participant={participant} />
|
||||
)}
|
||||
/>
|
||||
</Div>
|
||||
</Box>
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-3
@@ -6,7 +6,7 @@ import { useParticipants } from '@livekit/components-react'
|
||||
import { useWidgetInteraction } from '../../../hooks/useWidgetInteraction'
|
||||
|
||||
export const ParticipantsToggle = () => {
|
||||
const { t } = useTranslation('rooms')
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.participants' })
|
||||
|
||||
/**
|
||||
* Context could not be used due to inconsistent refresh behavior.
|
||||
@@ -30,10 +30,11 @@ export const ParticipantsToggle = () => {
|
||||
<ToggleButton
|
||||
square
|
||||
legacyStyle
|
||||
aria-label={t(`controls.participants.${tooltipLabel}`)}
|
||||
tooltip={t(`controls.participants.${tooltipLabel}`)}
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
isSelected={isParticipantsOpen}
|
||||
onPress={() => toggleParticipants()}
|
||||
data-attr={`controls-participants-${tooltipLabel}`}
|
||||
>
|
||||
<RiGroupLine />
|
||||
</ToggleButton>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Div, ToggleButton } from '@/primitives'
|
||||
import { RiArrowUpLine, RiCloseFill, RiRectangleLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTrackToggle, UseTrackToggleProps } from '@livekit/components-react'
|
||||
import { Track } from 'livekit-client'
|
||||
import React from 'react'
|
||||
|
||||
export const ScreenShareToggle = (
|
||||
props: Omit<
|
||||
UseTrackToggleProps<Track.Source.ScreenShare>,
|
||||
'source' | 'captureOptions'
|
||||
>
|
||||
) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.screenShare' })
|
||||
const { buttonProps, enabled } = useTrackToggle({
|
||||
...props,
|
||||
source: Track.Source.ScreenShare,
|
||||
captureOptions: { audio: true, selfBrowserSurface: 'include' },
|
||||
})
|
||||
|
||||
const tooltipLabel = enabled ? 'stop' : 'start'
|
||||
const Icon = enabled ? RiCloseFill : RiArrowUpLine
|
||||
|
||||
// fixme - remove ToggleButton custom styles when we design a proper icon
|
||||
return (
|
||||
<ToggleButton
|
||||
isSelected={enabled}
|
||||
square
|
||||
legacyStyle
|
||||
tooltip={t(tooltipLabel)}
|
||||
onPress={(e) =>
|
||||
buttonProps.onClick?.(
|
||||
e as unknown as React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
)
|
||||
}
|
||||
style={{
|
||||
maxWidth: '46px',
|
||||
maxHeight: '46px',
|
||||
}}
|
||||
data-attr={`controls-screenshare-${tooltipLabel}`}
|
||||
>
|
||||
<Div position="relative">
|
||||
<RiRectangleLine size={28} />
|
||||
<Icon
|
||||
size={16}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
/>
|
||||
</Div>
|
||||
</ToggleButton>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useMediaDeviceSelect,
|
||||
useTrackToggle,
|
||||
UseTrackToggleProps,
|
||||
} from '@livekit/components-react'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { Button, Menu, MenuList, ToggleButton } from '@/primitives'
|
||||
import {
|
||||
RemixiconComponentType,
|
||||
RiArrowDownSLine,
|
||||
RiMicLine,
|
||||
RiMicOffLine,
|
||||
RiVideoOffLine,
|
||||
RiVideoOnLine,
|
||||
} from '@remixicon/react'
|
||||
import { Track } from 'livekit-client'
|
||||
import React from 'react'
|
||||
|
||||
export type ToggleSource = Exclude<
|
||||
Track.Source,
|
||||
Track.Source.ScreenShareAudio | Track.Source.Unknown
|
||||
>
|
||||
|
||||
type SelectToggleSource = Exclude<ToggleSource, Track.Source.ScreenShare>
|
||||
|
||||
type SelectToggleDeviceConfig = {
|
||||
kind: MediaDeviceKind
|
||||
iconOn: RemixiconComponentType
|
||||
iconOff: RemixiconComponentType
|
||||
}
|
||||
|
||||
type SelectToggleDeviceConfigMap = {
|
||||
[key in SelectToggleSource]: SelectToggleDeviceConfig
|
||||
}
|
||||
|
||||
const selectToggleDeviceConfig: SelectToggleDeviceConfigMap = {
|
||||
[Track.Source.Microphone]: {
|
||||
kind: 'audioinput',
|
||||
iconOn: RiMicLine,
|
||||
iconOff: RiMicOffLine,
|
||||
},
|
||||
[Track.Source.Camera]: {
|
||||
kind: 'videoinput',
|
||||
iconOn: RiVideoOnLine,
|
||||
iconOff: RiVideoOffLine,
|
||||
},
|
||||
}
|
||||
|
||||
type SelectToggleDeviceProps<T extends ToggleSource> =
|
||||
UseTrackToggleProps<T> & {
|
||||
onActiveDeviceChange: (deviceId: string) => void
|
||||
source: SelectToggleSource
|
||||
}
|
||||
|
||||
export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
onActiveDeviceChange,
|
||||
...props
|
||||
}: SelectToggleDeviceProps<T>) => {
|
||||
const config = selectToggleDeviceConfig[props.source]
|
||||
if (!config) {
|
||||
throw new Error('Invalid source')
|
||||
}
|
||||
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
const { buttonProps, enabled } = useTrackToggle(props)
|
||||
|
||||
const { kind, iconOn, iconOff } = config
|
||||
|
||||
const { devices, activeDeviceId, setActiveMediaDevice } =
|
||||
useMediaDeviceSelect({ kind })
|
||||
|
||||
const toggleLabel = t(enabled ? 'disable' : 'enable', {
|
||||
keyPrefix: `join.${kind}`,
|
||||
})
|
||||
|
||||
const selectLabel = t('choose', { keyPrefix: `join.${kind}` })
|
||||
const Icon = enabled ? iconOn : iconOff
|
||||
|
||||
return (
|
||||
<HStack gap={0}>
|
||||
<ToggleButton
|
||||
isSelected={enabled}
|
||||
variant={enabled ? undefined : 'danger'}
|
||||
toggledStyles={false}
|
||||
onPress={(e) =>
|
||||
buttonProps.onClick?.(
|
||||
e as unknown as React.MouseEvent<HTMLButtonElement>
|
||||
)
|
||||
}
|
||||
aria-label={toggleLabel}
|
||||
tooltip={toggleLabel}
|
||||
groupPosition="left"
|
||||
>
|
||||
<Icon />
|
||||
</ToggleButton>
|
||||
<Menu>
|
||||
<Button
|
||||
tooltip={selectLabel}
|
||||
aria-label={selectLabel}
|
||||
groupPosition="right"
|
||||
square
|
||||
>
|
||||
<RiArrowDownSLine />
|
||||
</Button>
|
||||
<MenuList
|
||||
items={devices.map((d) => ({
|
||||
value: d.deviceId,
|
||||
label: d.label,
|
||||
}))}
|
||||
selectedItem={activeDeviceId}
|
||||
onAction={(value) => {
|
||||
setActiveMediaDevice(value as string)
|
||||
onActiveDeviceChange(value as string)
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { RoomEvent } from 'livekit-client'
|
||||
|
||||
// Issue: 'allRemoteParticipantRoomEvents' is not exposed or importable. One event is missing
|
||||
// to trigger the real-time update of participants when they change their name.
|
||||
// This code is duplicated from LiveKit.
|
||||
export const allRemoteParticipantRoomEvents = [
|
||||
RoomEvent.ConnectionStateChanged,
|
||||
RoomEvent.RoomMetadataChanged,
|
||||
|
||||
RoomEvent.ActiveSpeakersChanged,
|
||||
RoomEvent.ConnectionQualityChanged,
|
||||
|
||||
RoomEvent.ParticipantConnected,
|
||||
RoomEvent.ParticipantDisconnected,
|
||||
RoomEvent.ParticipantPermissionsChanged,
|
||||
RoomEvent.ParticipantMetadataChanged,
|
||||
RoomEvent.ParticipantNameChanged, // This element is missing in LiveKit and causes problems
|
||||
|
||||
RoomEvent.TrackMuted,
|
||||
RoomEvent.TrackUnmuted,
|
||||
RoomEvent.TrackPublished,
|
||||
RoomEvent.TrackUnpublished,
|
||||
RoomEvent.TrackStreamStateChanged,
|
||||
RoomEvent.TrackSubscriptionFailed,
|
||||
RoomEvent.TrackSubscriptionPermissionChanged,
|
||||
RoomEvent.TrackSubscriptionStatusChanged,
|
||||
]
|
||||
|
||||
export const allParticipantRoomEvents = [
|
||||
...allRemoteParticipantRoomEvents,
|
||||
RoomEvent.LocalTrackPublished,
|
||||
RoomEvent.LocalTrackUnpublished,
|
||||
]
|
||||
@@ -7,6 +7,7 @@ type useRaisedHandProps = {
|
||||
}
|
||||
|
||||
export function useRaisedHand({ participant }: useRaisedHandProps) {
|
||||
// fixme - refactor this part to rely on attributes
|
||||
const { metadata } = useParticipantInfo({ participant })
|
||||
const parsedMetadata = JSON.parse(metadata || '{}')
|
||||
|
||||
|
||||
@@ -1,34 +1,46 @@
|
||||
import { useLayoutContext } from '@livekit/components-react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { participantsStore } from '@/stores/participants.ts'
|
||||
import { layoutStore } from '@/stores/layout'
|
||||
|
||||
export const useWidgetInteraction = () => {
|
||||
const { dispatch, state } = useLayoutContext().widget
|
||||
|
||||
const participantsSnap = useSnapshot(participantsStore)
|
||||
const isParticipantsOpen = participantsSnap.showParticipants
|
||||
const layoutSnap = useSnapshot(layoutStore)
|
||||
const sidePanel = layoutSnap.sidePanel
|
||||
|
||||
const isParticipantsOpen = sidePanel == 'participants'
|
||||
const isEffectsOpen = sidePanel == 'effects'
|
||||
|
||||
const toggleParticipants = () => {
|
||||
if (dispatch && state?.showChat) {
|
||||
dispatch({ msg: 'toggle_chat' })
|
||||
}
|
||||
participantsStore.showParticipants = !isParticipantsOpen
|
||||
layoutStore.sidePanel = isParticipantsOpen ? null : 'participants'
|
||||
}
|
||||
|
||||
const toggleChat = () => {
|
||||
if (isParticipantsOpen) {
|
||||
participantsStore.showParticipants = false
|
||||
layoutStore.sidePanel = null
|
||||
}
|
||||
if (dispatch) {
|
||||
dispatch({ msg: 'toggle_chat' })
|
||||
}
|
||||
}
|
||||
|
||||
const toggleEffects = () => {
|
||||
if (dispatch && state?.showChat) {
|
||||
dispatch({ msg: 'toggle_chat' })
|
||||
}
|
||||
layoutStore.sidePanel = isEffectsOpen ? null : 'effects'
|
||||
}
|
||||
|
||||
return {
|
||||
toggleParticipants,
|
||||
toggleChat,
|
||||
toggleEffects,
|
||||
isChatOpen: state?.showChat,
|
||||
unreadMessages: state?.unreadMessages,
|
||||
isParticipantsOpen,
|
||||
isEffectsOpen,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@ import * as React from 'react'
|
||||
import { supportsScreenSharing } from '@livekit/components-core'
|
||||
|
||||
import {
|
||||
DisconnectButton,
|
||||
LeaveIcon,
|
||||
MediaDeviceMenu,
|
||||
TrackToggle,
|
||||
useMaybeLayoutContext,
|
||||
usePersistentUserChoices,
|
||||
} from '@livekit/components-react'
|
||||
@@ -15,11 +11,13 @@ import {
|
||||
import { mergeProps } from '@/utils/mergeProps.ts'
|
||||
import { StartMediaButton } from '../components/controls/StartMediaButton'
|
||||
import { useMediaQuery } from '../hooks/useMediaQuery'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { OptionsButton } from '../components/controls/Options/OptionsButton'
|
||||
import { ParticipantsToggle } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsToggle'
|
||||
import { ChatToggle } from '@/features/rooms/livekit/components/controls/ChatToggle'
|
||||
import { HandToggle } from '@/features/rooms/livekit/components/controls/HandToggle'
|
||||
import { ParticipantsToggle } from '../components/controls/Participants/ParticipantsToggle'
|
||||
import { ChatToggle } from '../components/controls/ChatToggle'
|
||||
import { HandToggle } from '../components/controls/HandToggle'
|
||||
import { SelectToggleDevice } from '../components/controls/SelectToggleDevice'
|
||||
import { LeaveButton } from '../components/controls/LeaveButton'
|
||||
import { ScreenShareToggle } from '../components/controls/ScreenShareToggle'
|
||||
|
||||
/** @public */
|
||||
export type ControlBarControls = {
|
||||
@@ -38,7 +36,7 @@ export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
controls?: ControlBarControls
|
||||
/**
|
||||
* If `true`, the user's device choices will be persisted.
|
||||
* This will enables the user to have the same device choices when they rejoin the room.
|
||||
* This will enable the user to have the same device choices when they rejoin the room.
|
||||
* @defaultValue true
|
||||
* @alpha
|
||||
*/
|
||||
@@ -67,7 +65,6 @@ export function ControlBar({
|
||||
onDeviceError,
|
||||
...props
|
||||
}: ControlBarProps) {
|
||||
const { t } = useTranslation('rooms')
|
||||
const [isChatOpen, setIsChatOpen] = React.useState(false)
|
||||
const layoutContext = useMaybeLayoutContext()
|
||||
React.useEffect(() => {
|
||||
@@ -83,26 +80,8 @@ export function ControlBar({
|
||||
const defaultVariation = isTooLittleSpace ? 'minimal' : 'verbose'
|
||||
variation ??= defaultVariation
|
||||
|
||||
const showIcon = React.useMemo(
|
||||
() => variation === 'minimal' || variation === 'verbose',
|
||||
[variation]
|
||||
)
|
||||
const showText = React.useMemo(
|
||||
() => variation === 'textOnly' || variation === 'verbose',
|
||||
[variation]
|
||||
)
|
||||
|
||||
const browserSupportsScreenSharing = supportsScreenSharing()
|
||||
|
||||
const [isScreenShareEnabled, setIsScreenShareEnabled] = React.useState(false)
|
||||
|
||||
const onScreenShareChange = React.useCallback(
|
||||
(enabled: boolean) => {
|
||||
setIsScreenShareEnabled(enabled)
|
||||
},
|
||||
[setIsScreenShareEnabled]
|
||||
)
|
||||
|
||||
const htmlProps = mergeProps({ className: 'lk-control-bar' }, props)
|
||||
|
||||
const {
|
||||
@@ -126,72 +105,38 @@ export function ControlBar({
|
||||
|
||||
return (
|
||||
<div {...htmlProps}>
|
||||
<div className="lk-button-group">
|
||||
<TrackToggle
|
||||
source={Track.Source.Microphone}
|
||||
showIcon={showIcon}
|
||||
onChange={microphoneOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||
}
|
||||
>
|
||||
{showText && t('controls.microphone')}
|
||||
</TrackToggle>
|
||||
<div className="lk-button-group-menu">
|
||||
<MediaDeviceMenu
|
||||
kind="audioinput"
|
||||
onActiveDeviceChange={(_kind, deviceId) =>
|
||||
saveAudioInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lk-button-group">
|
||||
<TrackToggle
|
||||
source={Track.Source.Camera}
|
||||
showIcon={showIcon}
|
||||
onChange={cameraOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||
}
|
||||
>
|
||||
{showText && t('controls.camera')}
|
||||
</TrackToggle>
|
||||
<div className="lk-button-group-menu">
|
||||
<MediaDeviceMenu
|
||||
kind="videoinput"
|
||||
onActiveDeviceChange={(_kind, deviceId) =>
|
||||
saveVideoInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Microphone}
|
||||
onChange={microphoneOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||
}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveAudioInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Camera}
|
||||
onChange={cameraOnChange}
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||
}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveVideoInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
/>
|
||||
{browserSupportsScreenSharing && (
|
||||
<TrackToggle
|
||||
source={Track.Source.ScreenShare}
|
||||
captureOptions={{ audio: true, selfBrowserSurface: 'include' }}
|
||||
showIcon={showIcon}
|
||||
onChange={onScreenShareChange}
|
||||
<ScreenShareToggle
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.ScreenShare, error })
|
||||
}
|
||||
>
|
||||
{showText &&
|
||||
t(
|
||||
isScreenShareEnabled
|
||||
? 'controls.stopScreenShare'
|
||||
: 'controls.shareScreen'
|
||||
)}
|
||||
</TrackToggle>
|
||||
/>
|
||||
)}
|
||||
<HandToggle />
|
||||
<ChatToggle />
|
||||
<ParticipantsToggle />
|
||||
<OptionsButton />
|
||||
<DisconnectButton>
|
||||
{showIcon && <LeaveIcon />}
|
||||
{showText && t('controls.leave')}
|
||||
</DisconnectButton>
|
||||
<LeaveButton />
|
||||
<StartMediaButton />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -30,13 +30,12 @@ import {
|
||||
import { ControlBar } from './ControlBar'
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { cva } from '@/styled-system/css'
|
||||
import { ParticipantsList } from '@/features/rooms/livekit/components/controls/Participants/ParticipantsList'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { participantsStore } from '@/stores/participants'
|
||||
import { layoutStore } from '@/stores/layout'
|
||||
import { FocusLayout } from '../components/FocusLayout'
|
||||
import { ParticipantTile } from '../components/ParticipantTile'
|
||||
import { SidePanel } from '../components/SidePanel'
|
||||
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
|
||||
import { RecordingIndicator } from '@/features/rooms/livekit/components/RecordingIndicator.tsx'
|
||||
|
||||
const LayoutWrapper = styled(
|
||||
'div',
|
||||
@@ -173,8 +172,8 @@ export function VideoConference({
|
||||
])
|
||||
/* eslint-enable react-hooks/exhaustive-deps */
|
||||
|
||||
const participantsSnap = useSnapshot(participantsStore)
|
||||
const showParticipants = participantsSnap.showParticipants
|
||||
const layoutSnap = useSnapshot(layoutStore)
|
||||
const sidePanel = layoutSnap.sidePanel
|
||||
|
||||
return (
|
||||
<div className="lk-video-conference" {...props}>
|
||||
@@ -185,7 +184,6 @@ export function VideoConference({
|
||||
onWidgetChange={widgetUpdate}
|
||||
>
|
||||
<div className="lk-video-conference-inner">
|
||||
<RecordingIndicator />
|
||||
<LayoutWrapper>
|
||||
<div
|
||||
style={{ display: 'flex', position: 'relative', width: '100%' }}
|
||||
@@ -225,7 +223,7 @@ export function VideoConference({
|
||||
messageEncoder={chatMessageEncoder}
|
||||
messageDecoder={chatMessageDecoder}
|
||||
/>
|
||||
{showParticipants && <ParticipantsList />}
|
||||
{sidePanel && <SidePanel />}
|
||||
</LayoutWrapper>
|
||||
<ControlBar />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useLanguageLabels } from '@/i18n/useLanguageLabels'
|
||||
import { A, Badge, Dialog, type DialogProps, Field, H, P } from '@/primitives'
|
||||
import { authUrl, logoutUrl, useUser } from '@/features/auth'
|
||||
import { logoutUrl, useUser } from '@/features/auth'
|
||||
import { ProConnectButton } from '@/components/ProConnectButton'
|
||||
|
||||
export type SettingsDialogProps = Pick<DialogProps, 'isOpen' | 'onOpenChange'>
|
||||
|
||||
@@ -28,9 +29,7 @@ export const SettingsDialog = (props: SettingsDialogProps) => {
|
||||
) : (
|
||||
<>
|
||||
<P>{t('account.youAreNotLoggedIn')}</P>
|
||||
<P>
|
||||
<A href={authUrl()}>{t('login', { ns: 'global' })}</A>
|
||||
</P>
|
||||
<ProConnectButton />
|
||||
</>
|
||||
)}
|
||||
<H lvl={2}>{t('language.heading')}</H>
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
usePersistentUserChoices,
|
||||
useRoomContext,
|
||||
} from '@livekit/components-react'
|
||||
import { authUrl, logoutUrl, useUser } from '@/features/auth'
|
||||
import { logoutUrl, useUser } from '@/features/auth'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { useState } from 'react'
|
||||
import { ProConnectButton } from '@/components/ProConnectButton'
|
||||
|
||||
export type AccountTabProps = Pick<DialogProps, 'onOpenChange'> &
|
||||
Pick<TabPanelProps, 'id'>
|
||||
@@ -58,9 +59,7 @@ export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => {
|
||||
) : (
|
||||
<>
|
||||
<P>{t('account.youAreNotLoggedIn')}</P>
|
||||
<P>
|
||||
<A href={authUrl()}>{t('login', { ns: 'global' })}</A>
|
||||
</P>
|
||||
<ProConnectButton />
|
||||
</>
|
||||
)}
|
||||
<HStack
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Crisp } from 'crisp-sdk-web'
|
||||
import { ApiUser } from '@/features/auth/api/ApiUser'
|
||||
|
||||
export const initializeSupportSession = (user: ApiUser) => {
|
||||
if (!Crisp.isCrispInjected()) return
|
||||
const { id, email } = user
|
||||
Crisp.setTokenId(`meet-${id}`)
|
||||
Crisp.user.setEmail(email)
|
||||
}
|
||||
|
||||
export const terminateSupportSession = () => {
|
||||
if (!Crisp.isCrispInjected()) return
|
||||
Crisp.setTokenId()
|
||||
Crisp.session.reset()
|
||||
}
|
||||
|
||||
export type useSupportProps = {
|
||||
id?: string
|
||||
}
|
||||
|
||||
// Configure Crisp chat for real-time support across all pages.
|
||||
export const useSupport = ({ id }: useSupportProps) => {
|
||||
useEffect(() => {
|
||||
if (!id || Crisp.isCrispInjected()) return
|
||||
Crisp.configure(id)
|
||||
Crisp.setHideOnMobile(true)
|
||||
}, [id])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -2,13 +2,16 @@ import { Link } from 'wouter'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { Stack } from '@/styled-system/jsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { A, Text, Button } from '@/primitives'
|
||||
import { Text, Button } from '@/primitives'
|
||||
import { SettingsButton } from '@/features/settings'
|
||||
import { authUrl, logoutUrl, useUser } from '@/features/auth'
|
||||
import { logoutUrl, useUser } from '@/features/auth'
|
||||
import { useMatchesRoute } from '@/navigation/useMatchesRoute'
|
||||
import { Feedback } from '@/components/Feedback'
|
||||
import { Menu } from '@/primitives/Menu'
|
||||
import { MenuList } from '@/primitives/MenuList'
|
||||
import { ProConnectButton } from '@/components/ProConnectButton'
|
||||
import { terminateAnalyticsSession } from '@/features/analytics/hooks/useAnalytics'
|
||||
import { terminateSupportSession } from '@/features/support/hooks/useSupport'
|
||||
|
||||
export const Header = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -63,7 +66,7 @@ export const Header = () => {
|
||||
<nav>
|
||||
<Stack gap={1} direction="row" align="center">
|
||||
{isLoggedIn === false && !isHome && (
|
||||
<A href={authUrl()}>{t('login')}</A>
|
||||
<ProConnectButton hint={false} />
|
||||
)}
|
||||
{!!user && (
|
||||
<Menu>
|
||||
@@ -79,6 +82,8 @@ export const Header = () => {
|
||||
items={[{ value: 'logout', label: t('logout') }]}
|
||||
onAction={(value) => {
|
||||
if (value === 'logout') {
|
||||
terminateAnalyticsSession()
|
||||
terminateSupportSession()
|
||||
window.location.href = logoutUrl()
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "Meet",
|
||||
"app": "Visio",
|
||||
"backToHome": "",
|
||||
"cancel": "",
|
||||
"closeDialog": "",
|
||||
@@ -12,7 +12,11 @@
|
||||
},
|
||||
"loading": "",
|
||||
"loggedInUserTooltip": "",
|
||||
"login": "Anmelden",
|
||||
"login": {
|
||||
"buttonLabel": "",
|
||||
"linkLabel": "",
|
||||
"link": ""
|
||||
},
|
||||
"logout": "",
|
||||
"notFound": {
|
||||
"heading": ""
|
||||
|
||||
@@ -4,11 +4,27 @@
|
||||
"heading": ""
|
||||
},
|
||||
"join": {
|
||||
"camlabel": "",
|
||||
"videoinput": {
|
||||
"choose": "",
|
||||
"disable": "",
|
||||
"enable": "",
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "",
|
||||
"disable": "",
|
||||
"enable": "",
|
||||
"label": ""
|
||||
},
|
||||
"heading": "",
|
||||
"joinLabel": "",
|
||||
"micLabel": "",
|
||||
"userLabel": ""
|
||||
"joinMeeting": "",
|
||||
"toggleOff": "",
|
||||
"toggleOn": "",
|
||||
"userLabel": "",
|
||||
"usernameHint": "",
|
||||
"usernameLabel": ""
|
||||
},
|
||||
"leaveRoomPrompt": "",
|
||||
"shareDialog": {
|
||||
@@ -37,6 +53,10 @@
|
||||
"raise": "",
|
||||
"lower": ""
|
||||
},
|
||||
"screenShare": {
|
||||
"start": "",
|
||||
"stop": ""
|
||||
},
|
||||
"leave": "",
|
||||
"participants": {
|
||||
"open": "",
|
||||
@@ -49,13 +69,35 @@
|
||||
"feedbacks": "",
|
||||
"support": "",
|
||||
"settings": "",
|
||||
"username": ""
|
||||
"username": "",
|
||||
"effects": ""
|
||||
}
|
||||
},
|
||||
"participants": {
|
||||
"effects": {
|
||||
"activateCamera": "",
|
||||
"notAvailable": "",
|
||||
"heading": "",
|
||||
"blur": {
|
||||
"light": "",
|
||||
"normal": "",
|
||||
"apply": "",
|
||||
"clear": ""
|
||||
},
|
||||
"experimental": ""
|
||||
},
|
||||
"sidePanel": {
|
||||
"heading": {
|
||||
"participants": "",
|
||||
"effects": ""
|
||||
},
|
||||
"content": {
|
||||
"participants": "",
|
||||
"effects": ""
|
||||
},
|
||||
"closeButton": ""
|
||||
},
|
||||
"participants": {
|
||||
"subheading": "",
|
||||
"closeButton": "",
|
||||
"contributors": "",
|
||||
"collapsable": {
|
||||
"open": "",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"app": "Meet",
|
||||
"app": "Visio",
|
||||
"backToHome": "Back to homescreen",
|
||||
"cancel": "Cancel",
|
||||
"closeDialog": "Close dialog",
|
||||
"error": {
|
||||
"heading": "An error occured while loading the page"
|
||||
"heading": "An error occurred while loading the page"
|
||||
},
|
||||
"feedbackAlert": "Give us feedback",
|
||||
"forbidden": {
|
||||
@@ -12,7 +12,11 @@
|
||||
},
|
||||
"loading": "Loading…",
|
||||
"loggedInUserTooltip": "Logged in as…",
|
||||
"login": "Login",
|
||||
"login": {
|
||||
"buttonLabel": "Login with ProConnect",
|
||||
"linkLabel": "What is ProConnect? - new tab",
|
||||
"link": "What is ProConnect?"
|
||||
},
|
||||
"logout": "Logout",
|
||||
"notFound": {
|
||||
"heading": "Page not found"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"createMeeting": "Create a meeting",
|
||||
"heading": "Welcome in Meet",
|
||||
"heading": "Welcome in Visio",
|
||||
"intro": "Work easily, from anywhere.",
|
||||
"joinInputError": "Use a meeting link or code. Examples:",
|
||||
"joinInputExample": "URL or 10-letter code",
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
{
|
||||
"feedback": {
|
||||
"body": "Please fill out the form available in the header to give us your precious feedback! Thanks.",
|
||||
"heading": "Help us improve Meet"
|
||||
"heading": "Help us improve Visio"
|
||||
},
|
||||
"join": {
|
||||
"camlabel": "Camera",
|
||||
"heading": "Join the meeting",
|
||||
"videoinput": {
|
||||
"choose": "Select camera",
|
||||
"disable": "Disable camera",
|
||||
"enable": "Enable camera",
|
||||
"label": "Camera",
|
||||
"placeholder": "Enable camera to see the preview"
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Select microphone",
|
||||
"disable": "Disable microphone",
|
||||
"enable": "Enable microphone",
|
||||
"label": "Microphone"
|
||||
},
|
||||
"heading": "Verify your settings",
|
||||
"joinLabel": "Join",
|
||||
"micLabel": "Microphone",
|
||||
"userLabel": "Your name"
|
||||
"joinMeeting": "Join meeting",
|
||||
"toggleOff": "Click to turn off",
|
||||
"toggleOn": "Click to turn on",
|
||||
"userLabel": "",
|
||||
"usernameHint": "Shown to other participants",
|
||||
"usernameLabel": "Your name"
|
||||
},
|
||||
"leaveRoomPrompt": "This will make you leave the meeting.",
|
||||
"shareDialog": {
|
||||
@@ -27,8 +43,6 @@
|
||||
"controls": {
|
||||
"microphone": "Microphone",
|
||||
"camera": "Camera",
|
||||
"shareScreen": "Share screen",
|
||||
"stopScreenShare": "Stop screen share",
|
||||
"chat": {
|
||||
"open": "Close the chat",
|
||||
"closed": "Open the chat"
|
||||
@@ -37,6 +51,10 @@
|
||||
"raise": "Raise hand",
|
||||
"lower": "Lower hand"
|
||||
},
|
||||
"screenShare": {
|
||||
"start": "Share screen",
|
||||
"stop": "Stop screen share"
|
||||
},
|
||||
"leave": "Leave",
|
||||
"participants": {
|
||||
"open": "Hide everyone",
|
||||
@@ -49,13 +67,35 @@
|
||||
"feedbacks": "Give us feedbacks",
|
||||
"support": "Get Help on Tchap",
|
||||
"settings": "Settings",
|
||||
"username": "Update Your Name"
|
||||
"username": "Update Your Name",
|
||||
"effects": "Apply effects"
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"activateCamera": "Your camera is disabled. Choose an option to enable it.",
|
||||
"notAvailable": "The blur effect will be available soon on your browser. We're working on it! In the meantime, you can use Google Chrome :(",
|
||||
"heading": "Blur",
|
||||
"blur": {
|
||||
"light": "Light blur",
|
||||
"normal": "Blur",
|
||||
"apply": "Enable blur",
|
||||
"clear": "Disable blur"
|
||||
},
|
||||
"experimental": "Experimental feature. A v2 is coming for full browser support and improved quality."
|
||||
},
|
||||
"sidePanel": {
|
||||
"heading": {
|
||||
"participants": "Participants",
|
||||
"effects": "Effects"
|
||||
},
|
||||
"content": {
|
||||
"participants": "participants",
|
||||
"effects": "effects"
|
||||
},
|
||||
"closeButton": "Hide {{content}}"
|
||||
},
|
||||
"participants": {
|
||||
"heading": "Participants",
|
||||
"subheading": "In room",
|
||||
"closeButton": "Hide participants",
|
||||
"you": "You",
|
||||
"contributors": "Contributors",
|
||||
"collapsable": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"currentlyLoggedAs": "You are currently logged in as <0>{{user}}</0>",
|
||||
"heading": "Account",
|
||||
"youAreNotLoggedIn": "You are not logged in.",
|
||||
"nameLabel": "Votre Nom",
|
||||
"nameLabel": "Your Name",
|
||||
"authentication": "Authentication"
|
||||
},
|
||||
"audio": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "Meet",
|
||||
"app": "Visio",
|
||||
"backToHome": "Retour à l'accueil",
|
||||
"cancel": "Annuler",
|
||||
"closeDialog": "Fermer la fenêtre modale",
|
||||
@@ -12,7 +12,11 @@
|
||||
},
|
||||
"loading": "Chargement…",
|
||||
"loggedInUserTooltip": "Connecté en tant que…",
|
||||
"login": "Se connecter",
|
||||
"login": {
|
||||
"buttonLabel": "S'identifier avec ProConnect",
|
||||
"linkLabel": "Qu'est-ce que ProConnect ? - nouvelle fenêtre",
|
||||
"link": "Qu'est-ce que ProConnect ?"
|
||||
},
|
||||
"logout": "Se déconnecter",
|
||||
"notFound": {
|
||||
"heading": "Page introuvable"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"createMeeting": "Créer une réunion",
|
||||
"heading": "Meet",
|
||||
"heading": "Visio",
|
||||
"intro": "Collaborez en toute simplicité, où que vous soyez.",
|
||||
"joinInputError": "Saisissez un lien ou un code de réunion. Exemples :",
|
||||
"joinInputExample": "Un code de réunion ressemble à ceci : abc-defg-hij",
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
{
|
||||
"feedback": {
|
||||
"body": "Remplissez le formulaire disponible dans l'entête du site pour nous donner votre avis sur l'outil. Vos retours sont précieux ! Merci.",
|
||||
"heading": "Aidez-nous à améliorer Meet"
|
||||
"heading": "Aidez-nous à améliorer Visio"
|
||||
},
|
||||
"join": {
|
||||
"camlabel": "Webcam",
|
||||
"heading": "Rejoindre la réunion",
|
||||
"videoinput": {
|
||||
"choose": "Choisir la webcam",
|
||||
"disable": "Désactiver la webcam",
|
||||
"enable": "Activer la webcam",
|
||||
"label": "Webcam",
|
||||
"placeholder": "Activez la webcam pour prévisualiser l'affichage"
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Choisir le micro",
|
||||
"disable": "Désactiver le micro",
|
||||
"enable": "Activer le micro",
|
||||
"label": "Microphone"
|
||||
},
|
||||
"heading": "Vérifiez vos paramètres",
|
||||
"joinLabel": "Rejoindre",
|
||||
"micLabel": "Micro",
|
||||
"userLabel": "Votre nom"
|
||||
"joinMeeting": "Rejoindre la réjoindre",
|
||||
"toggleOff": "Cliquez pour désactiver",
|
||||
"toggleOn": "Cliquez pour activer",
|
||||
"userLabel": "",
|
||||
"usernameHint": "Affiché aux autres participants",
|
||||
"usernameLabel": "Votre nom"
|
||||
},
|
||||
"leaveRoomPrompt": "Revenir à l'accueil vous fera quitter la réunion.",
|
||||
"shareDialog": {
|
||||
@@ -27,8 +43,6 @@
|
||||
"controls": {
|
||||
"microphone": "Microphone",
|
||||
"camera": "Camera",
|
||||
"shareScreen": "Partager l'écran",
|
||||
"stopScreenShare": "Arrêter le partage",
|
||||
"chat": {
|
||||
"open": "Masquer le chat",
|
||||
"closed": "Afficher le chat"
|
||||
@@ -37,6 +51,10 @@
|
||||
"raise": "Lever la main",
|
||||
"lower": "Baisser la main"
|
||||
},
|
||||
"screenShare": {
|
||||
"start": "Partager l'écran",
|
||||
"stop": "Arrêter le partage"
|
||||
},
|
||||
"leave": "Quitter",
|
||||
"participants": {
|
||||
"open": "Masquer les participants",
|
||||
@@ -49,13 +67,35 @@
|
||||
"feedbacks": "Partager votre avis",
|
||||
"support": "Obtenir de l'aide sur Tchap",
|
||||
"settings": "Paramètres",
|
||||
"username": "Choisir votre nom"
|
||||
"username": "Choisir votre nom",
|
||||
"effects": "Appliquer des effets"
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"activateCamera": "Votre camera est désactivée. Choisissez une option pour l'activer.",
|
||||
"notAvailable": "L'effet de flou sera bientôt disponible sur votre navigateur. Nous y travaillons ! En attendant, vous pouvez utiliser Google Chrome :(",
|
||||
"heading": "Flou",
|
||||
"blur": {
|
||||
"light": "Léger flou",
|
||||
"normal": "Flou",
|
||||
"apply": "Appliquer le flou",
|
||||
"clear": "Désactiver le flou"
|
||||
},
|
||||
"experimental": "Fonctionnalité expérimentale. Une v2 arrive pour un support complet sur tous les navigateurs et une meilleur qualité."
|
||||
},
|
||||
"sidePanel": {
|
||||
"heading": {
|
||||
"participants": "Participants",
|
||||
"effects": "Effets"
|
||||
},
|
||||
"content": {
|
||||
"participants": "les participants",
|
||||
"effects": "les effets"
|
||||
},
|
||||
"closeButton": "Masquer {{content}}"
|
||||
},
|
||||
"participants": {
|
||||
"heading": "Participants",
|
||||
"subheading": "Dans la réunion",
|
||||
"closeButton": "Masquer les participants",
|
||||
"you": "Vous",
|
||||
"contributors": "Contributeurs",
|
||||
"collapsable": {
|
||||
|
||||
@@ -134,10 +134,11 @@ export const Checkbox = ({
|
||||
>
|
||||
<StyledCheckbox {...props}>
|
||||
{(renderProps) => {
|
||||
renderProps.isInvalid && !!props.validate
|
||||
? setError(props.validate(renderProps.isSelected))
|
||||
: setError(null)
|
||||
|
||||
if (renderProps.isInvalid && !!props.validate) {
|
||||
setError(props.validate(renderProps.isSelected))
|
||||
} else {
|
||||
setError(null)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="mt-Checkbox-checkbox">
|
||||
|
||||
@@ -26,7 +26,7 @@ export const MenuList = <T extends string | number = string>({
|
||||
const label = typeof item === 'string' ? item : item.label
|
||||
return (
|
||||
<MenuItem
|
||||
className={menuItemRecipe()}
|
||||
className={menuItemRecipe({ extraPadding: true })}
|
||||
key={value}
|
||||
id={value as string}
|
||||
onAction={() => {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { Separator as RACSeparator } from 'react-aria-components'
|
||||
|
||||
export const Separator = styled(RACSeparator, {
|
||||
base: {
|
||||
height: '1px',
|
||||
background: 'gray.400',
|
||||
margin: '4px 0',
|
||||
},
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import { proxy } from 'valtio'
|
||||
|
||||
type State = {
|
||||
egressId: string | undefined
|
||||
}
|
||||
|
||||
export const egressStore = proxy<State>({
|
||||
egressId: undefined,
|
||||
})
|
||||
@@ -2,8 +2,10 @@ import { proxy } from 'valtio'
|
||||
|
||||
type State = {
|
||||
showHeader: boolean
|
||||
sidePanel: 'participants' | 'effects' | null
|
||||
}
|
||||
|
||||
export const layoutStore = proxy<State>({
|
||||
showHeader: false,
|
||||
sidePanel: null,
|
||||
})
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { proxy } from 'valtio'
|
||||
|
||||
type State = {
|
||||
showParticipants: boolean
|
||||
}
|
||||
|
||||
export const participantsStore = proxy<State>({
|
||||
showParticipants: false,
|
||||
})
|
||||
@@ -18,65 +18,74 @@ sops:
|
||||
- recipient: age15fyxdwmg5mvldtqqus87xspuws2u0cpvwheehrtvkexj4tnsqqysw6re2x
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwZG8rTjAzcVNtVHdLZmJI
|
||||
Mm4vemNrV1dDUExEZ1R1UGJmWjN3aWw1VXc0CjF1MVhhUTV6ckl1OEFNSjBJUCta
|
||||
MTE3QU04RDJKMWlWcHhDSG1NTmZyTkEKLS0tIHh5UHRqckUxZWZLUDl4d3FDdHJs
|
||||
Y0VMc0llMytmMWNUOW56d3ltTHpwZ2cKEHkn6wuHNeMTk+E6nEMpEJZ6wpdXSi3k
|
||||
FkzXRa6SudAgA4R6K9EieHKPdiNvi0IsCJOhLpiNQtENu9poF5ozqQ==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCQnRJY0xWNFVRYkR0QTND
|
||||
Rkc5dDRITUpRMW1EVlhEUk9xN0ttbElrdEJvClBMVnVKMXhGMThTcTVaN0s3OG1v
|
||||
S0U2UUNYZ3FzcXUwVXRyS1hPZkpxcmsKLS0tIHhWei9KVmxFaXgzRUxmMWU2RmhG
|
||||
Y1ZQekpQS3poSWxHZXUvbitlc2lIczAKjLZlgUSz51W4GHirUn352eFPSxIK1/Wf
|
||||
N+kzoUvMLmwwfHDztFFYLOEE9x1zx7GoPGG7fN9bTG7GWgcRdd7NUw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age16hnlml8yv4ynwy0seer57g8qww075crd0g7nsundz3pj4wk7m3vqftszg7
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAva3lFTTM2ZUJpcnozNmJt
|
||||
ZHAvRndkSjRCVEZYN29wMWFZVmV5ekNiYW5FCkI3RWUvVTdVZ2dJeWVSY085SW5G
|
||||
UWNOempRQlFCUUlmL0RIR1ZZZVI5cDQKLS0tIEpwZkVodENLM1VnQ1o1TmhRSFFi
|
||||
ZEF5MUNVdHRKcW9aK0M2M0ZVNDBxbjgKw8Wp06PcoStrO6ppsOR5zWjXbYrP64Dv
|
||||
XAEHQMa7vyvuu12YIa/fpyDM4HrsrPq3OWudxuWS6p0X8xmPYrXm6g==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3aTVDMDNUUFFVQlE3Ykpl
|
||||
ZlFQUHQ0UUdlTU54WlEvanBWeGVlYnd1aEZFClQ3Y1ZSOHp6QUt1UXBLV3NWaGJ4
|
||||
T2ZwKzZXcXh1Z2llQk9SN1RRLzNOdGcKLS0tIHN0YTFpL05SQUc1WTRsQ2Z0ZGtu
|
||||
ZjhoUHhFbkMvRUhscy9LY05xbXFZVlEKXrt6UPLOprcnsTFX1WCF/pIWXmjiPGEx
|
||||
7NNbnAl+A9yfheCeiJdWcj5ZBCa6Nx2udVMNjQ7ITMzhIY4BBSicuQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1plkp8td6zzfcavjusmsfrlk54t9vn8jjxm8zaz7cmnr7kzl2nfnsd54hwg
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1OU9mZHZFanpTQjE3aGly
|
||||
VVZDWFh5ZmRHV1YwWUtsZVd6R090SnkxVVNVCmM1UWwwaFlqTHlhSGFRblZBbVhl
|
||||
aEhtWWp1N1lVTDNqOFJBL0swVFl3Y3cKLS0tIG5ZTHAveFdLdGhQeUc2Tzh1d0dw
|
||||
UzRhNGNTZFR3U3BLNFhCdFp6aU5uZVEKL4K5jFjPfMp/fMA8+nQerj6PE5zvGeHW
|
||||
1SuHwnDiglKmksj8Gy+7spwLQCmo11JCnW2gXKktVIe5XOyhortq3Q==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1Y3JyU3JPOWdiamd0cEFV
|
||||
UWRUdUQ5V3ZnQXQ0YURLZ3RiY0toN29vNkhJCnIzYWdkV3puVWQ5Nm5SVGpobllB
|
||||
OW5WWkVLaE55eUs2OWNDWkZiOGNuZW8KLS0tIHN3eGVST1BRMU1JRWNRZGNJeGdi
|
||||
SWJXQ2VMZ3kyWElBSjBKc0tQWmIwMjgKLDakU3iHwVTQBYGR0d2TFFdLdsct49y1
|
||||
/S8vydlcx08L0yWHbfamhiYJE3BZbRXZue6z5irBPKEVjGD42aSREw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age12g6f5fse25tgrwweleh4jls3qs52hey2edh759smulwmk5lnzadslu2cp3
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Z1lXM0tDRzJnMytDZHBV
|
||||
ZlpCcTg0U3RGeEVzN2dBMkFYa0FBMFVKMWw4Cm5KeWMvdFBRNHFJWFNSS3NVWlNQ
|
||||
dnhkc2F2MFVTMmVHT2U2STdUWWcvNzAKLS0tIHkzQXltQW5TWHVrektQU1hES1ll
|
||||
UnVld1laTWRmTlkzWHRKRjJmWVJhbU0KANxoSnhDbxsja+Eo8MVCGv6iThNmlo/m
|
||||
y0RXVNVqNlqTreT0F/SKmP74W3lF30nwsfrOywMQyu75k8p5MGwUuQ==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKUklKTzRzdHZiZjdHSmY2
|
||||
WCs3ZFVodFBGcDdvZWZaaHNnbG9mdFk2WVJjCjUwZjA2Qi9CR1JYdjJ3VFlhZDZR
|
||||
REEyZStrTjIwbWdCdVU5NWo2Y2hBemsKLS0tIFZmWVh0Z1BFUGRjZExMNlNJSFVJ
|
||||
VnlWY3U4dW4ycXJCOVVUUlJxYzVONGsKy47vHe+awhJyI/pSUOhvUiOt+jtn+Vwg
|
||||
Rlm93bJr0GNVWYm521r/I409s3TMitQerweGnl3u7t1FaKX7oI1qBQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1tl80n23wq6zxegupwn70ew0yp225ua5v4dk800x7g2w6pvlxz46qk592pa
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwUm9oT3RYbGZXaW5CM20r
|
||||
QlYyeGZjMG05MFNzdndCU2dUemZqdXF5Um1FCkZWdVlubW1MZ3NwM3V2S3dQRFA1
|
||||
cHVvOXBMMGtMdkF4T2s3Wk9wYkJUZUEKLS0tIFBRM3FWYTRrVDNwZStwQWlpbEYx
|
||||
dXA5WE5udnhhMVdLTk1jMzJuU3VWaTQKaxTpyqi5fjmOFR4qOxm+wSqWDxb/96QI
|
||||
rHGUI+CqMVKZ6w1fMH0uanvCuw7Q9rbmsKB+DsjARMLFGqxzP/psUA==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVUE9nVk1SMUp5WW9zYnZs
|
||||
OFBYWmpwZW56S0J0Qk43dmlCWG5RV0RIKzM0CkVNbzI5VSs3VVdaT2xnVEhGdW9h
|
||||
QW02K0ZmeGxsSDZMTGdETjkwd2xVd2MKLS0tIHp2WWQyUFphMmlZaDBZR0JQZ095
|
||||
UTVaYXl6SkZUNElUdUlZcU8rditFd3cK0CqMiDzEItfB/T0K8YEncp9HuKWVTy7q
|
||||
2LgHBQJi35sdBviEpsHZt7BlHTKanbmB5S9oUexNq+3wUP9e1n5CGA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1qy04neuzwpasmvljqrcvhwnf0kz5cpyteze38c8avp0czewskasszv9pyw
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwMmVOaHV1Unl4UmUrVXEy
|
||||
REJIYWZhQmt3R0R5RFRveTl4Z0JaZlpKNmd3Ckl0dkFUaG90TE5odVh0YVprMVZj
|
||||
T2tXS0dSRGVVczdhb1dlVlV0L3JZUncKLS0tIGo2RDBPZXNzU2dQQlJhY0NBS2ps
|
||||
RW92OFVTS3d2L3UxOXNrVUFSVFI5TXMKn3pdHbXxBccG6Q+gWPVQK/5wiIKkzdhm
|
||||
HiNzezStrkFHf/lsFS2LNgYkfMMzBQ4rJj7oQkrD1Z8j6qRld3zzLg==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0VDdRaEVCemd5T0ozSmtp
|
||||
NllJa0tURVhxTXlZQS8vNUZxbGFQVnhRMDJjClhlaE53WTRpeEtVSGErYUdyZk1h
|
||||
SloxZGNUdFRDZ1J4bzdneFUyNXdXdkUKLS0tIGdxU3RCUEVmcTdnZnFidkROdXJS
|
||||
dVdQUUdVUUhpVVh6SytRV1V0RHV6cDQKagJA6w43n8yh119PrJltPPh/EbHIKjfH
|
||||
G8RfgXBrf5iWdWsyD8haDL6WsjdbVQsbPCz+ucULfnZ1Dn8A/3yQUQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age18fgn6j2vwwswqcpv9xpcehq8mrf9zs2sglwkamp3tzwx8d9jq9jsrskrk9
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzeWN4ZE5Ya3RPV2VKRFdv
|
||||
UGQ4QTJUZFovSXpQREhNWXdQbktVUHFQaUhjCmM1dXNxbGdlVjBWNjc5L2E5QlBy
|
||||
NjI0S251cjZvYU1nQ2swWXVDdjdhSG8KLS0tIHdFNXptN1VBNytFNW40MUsxTGFM
|
||||
WlVnc3VLY1ZReDFxUVlqWU9rM0xMQ00Kq/ckdP1N4BDBo0pSH9pp36skIacwNm3d
|
||||
utiuCgG16Hqe6YDb3agx81BocuSJ9oMOdpegoztnkkBDDP0F+e/VuQ==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIbGgxRG5sZHFPZm5OTnZw
|
||||
L21JUnFDOTNZUitYNHJad3FnYkk2aDlQRzM0CnI1NXZDcEtQdjBWaVhJMElIOEZU
|
||||
KzFjT0p4OVpzUFlnR1Q5cm5SVnhGaEUKLS0tIFliVVQ4VFg4QVJxazdyZG9PbE9M
|
||||
M3NzZFRKMFkxZHZ1Rmh0VWFWMDZTQlEKxKVEvnnV56t/RSvP94TgjW1Do68Tn2N2
|
||||
tJMI8VIp7rs5vxaToois6CMuhVONsl1CBozAEV8pqS5O830RGa1eog==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1hm2hsfgjezpsc3k0y5w5feq9t8vl3seq04qjhgt6ztd6403wfvpsgxu09m
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBta3VwOGQrSHdENGZLZUpq
|
||||
SGw0Z2YwYm8vZ0h4Y01UclIrQlRVNWVJaHlRCmFMQkp1aGc5R1JrMVdPNmwvbkYv
|
||||
NkxOc3pYY2E5dVVFejZJaitSb0hRUVUKLS0tIGFzclh3VXhBc3NYQjZlUmNUSk10
|
||||
S2xqMEpBcEpaOHdBNUtvOWxsVUhLLzQKGT3grtnocBkaCKXbtH7cZu/ZP9MKsAjt
|
||||
ZNmS1GahpBx2lVEbfJNLfwId+IJ6kOIyHj42g9yIQFfKTPKE32Ht8A==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2024-07-18T13:08:42Z"
|
||||
mac: ENC[AES256_GCM,data:a/uHyw9V/SMIePV9nPf+wJgPg+YDYLJGYy7NMLBrBgCXtBWHHonSNjzdmtjix1bW2y+cU0gMqodrtqR1cJGBmXr4NRY7NJqgLWE9rEdYfG7BnfqsWmvAaTIrSs7QMZWkEic7ys/bXoA5BZoau3olhVqIO2A/iyBtoMU9Hv7hPlo=,iv:gaqSCUbN7cxWPNrFPDTl7xNxpOZL6GY/swD/MDCiRqk=,tag:Oz0f/DyD3KGV/9Rprj/1Xw==,type:str]
|
||||
|
||||
@@ -49,6 +49,8 @@ backend:
|
||||
LIVEKIT_API_URL: https://livekit.127.0.0.1.nip.io/
|
||||
ANALYTICS_KEY: xwhoIMCZ8PBRjQ2t
|
||||
ALLOW_UNREGISTERED_ROOMS: False
|
||||
FRONTEND_SILENCE_LIVEKIT_DEBUG: False
|
||||
FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041'}"
|
||||
|
||||
|
||||
migrate:
|
||||
@@ -95,3 +97,11 @@ ingress:
|
||||
ingressAdmin:
|
||||
enabled: true
|
||||
host: meet.127.0.0.1.nip.io
|
||||
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
image:
|
||||
repository: lasuite/meet-backend
|
||||
pullPolicy: Always
|
||||
tag: "v0.1.5"
|
||||
|
||||
backend:
|
||||
migrateJobAnnotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||
envVars:
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS: http://meet-preprod.beta.numerique.gouv.fr,https://meet-preprod.beta.numerique.gouv.fr
|
||||
DJANGO_CONFIGURATION: Production
|
||||
DJANGO_ALLOWED_HOSTS: meet-preprod.beta.numerique.gouv.fr
|
||||
DJANGO_SUPERUSER_EMAIL:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: DJANGO_SUPERUSER_EMAIL
|
||||
DJANGO_SECRET_KEY:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: DJANGO_SECRET_KEY
|
||||
DJANGO_SETTINGS_MODULE: meet.settings
|
||||
DJANGO_SILENCED_SYSTEM_CHECKS: security.W004, security.W008
|
||||
DJANGO_SUPERUSER_PASSWORD:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: DJANGO_SUPERUSER_PASSWORD
|
||||
DJANGO_EMAIL_HOST: "snap-mail.numerique.gouv.fr"
|
||||
DJANGO_EMAIL_PORT: 465
|
||||
DJANGO_EMAIL_USE_SSL: True
|
||||
OIDC_OP_JWKS_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/jwks
|
||||
OIDC_OP_AUTHORIZATION_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/authorize
|
||||
OIDC_OP_TOKEN_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/token
|
||||
OIDC_OP_USER_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/userinfo
|
||||
OIDC_OP_LOGOUT_ENDPOINT: https://fca.integ01.dev-agentconnect.fr/api/v2/session/end
|
||||
OIDC_RP_CLIENT_ID:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: OIDC_RP_CLIENT_ID
|
||||
OIDC_RP_CLIENT_SECRET:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: OIDC_RP_CLIENT_SECRET
|
||||
OIDC_RP_SIGN_ALGO: RS256
|
||||
OIDC_RP_SCOPES: "openid email"
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://meet-preprod.beta.numerique.gouv.fr
|
||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
|
||||
LOGIN_REDIRECT_URL: https://meet-preprod.beta.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://meet-preprod.beta.numerique.gouv.fr
|
||||
LOGOUT_REDIRECT_URL: https://meet-preprod.beta.numerique.gouv.fr
|
||||
DB_HOST:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: host
|
||||
DB_NAME:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: database
|
||||
DB_USER:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: username
|
||||
DB_PASSWORD:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: password
|
||||
DB_PORT:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: port
|
||||
POSTGRES_USER:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: username
|
||||
POSTGRES_DB:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: database
|
||||
POSTGRES_PASSWORD:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
key: password
|
||||
REDIS_URL:
|
||||
secretKeyRef:
|
||||
name: redis.redis.libre.sh
|
||||
key: url
|
||||
STORAGES_STATICFILES_BACKEND: django.contrib.staticfiles.storage.StaticFilesStorage
|
||||
LIVEKIT_API_SECRET:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: LIVEKIT_API_SECRET
|
||||
LIVEKIT_API_KEY:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
key: LIVEKIT_API_KEY
|
||||
LIVEKIT_API_URL: https://livekit-preprod.beta.numerique.gouv.fr
|
||||
ALLOW_UNREGISTERED_ROOMS: False
|
||||
|
||||
createsuperuser:
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-c"
|
||||
- |
|
||||
python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD
|
||||
restartPolicy: Never
|
||||
|
||||
frontend:
|
||||
image:
|
||||
repository: lasuite/meet-frontend
|
||||
pullPolicy: Always
|
||||
tag: "v0.1.5"
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: meet-preprod.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
|
||||
ingressAdmin:
|
||||
enabled: true
|
||||
host: meet-preprod.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/auth-signin: https://oauth2-proxy-preprod.beta.numerique.gouv.fr/oauth2/start
|
||||
nginx.ingress.kubernetes.io/auth-url: https://oauth2-proxy-preprod.beta.numerique.gouv.fr/oauth2/auth
|
||||
@@ -1,16 +1,16 @@
|
||||
image:
|
||||
repository: lasuite/meet-backend
|
||||
pullPolicy: Always
|
||||
tag: "v0.1.5"
|
||||
tag: "v0.1.6"
|
||||
|
||||
backend:
|
||||
migrateJobAnnotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||
envVars:
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS: https://meet.numerique.gouv.fr
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS: https://visio.numerique.gouv.fr,https://meet.numerique.gouv.fr
|
||||
DJANGO_CONFIGURATION: Production
|
||||
DJANGO_ALLOWED_HOSTS: meet.numerique.gouv.fr
|
||||
DJANGO_ALLOWED_HOSTS: visio.numerique.gouv.fr,meet.numerique.gouv.fr
|
||||
DJANGO_SECRET_KEY:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
@@ -43,11 +43,11 @@ backend:
|
||||
key: OIDC_RP_CLIENT_SECRET
|
||||
OIDC_RP_SIGN_ALGO: RS256
|
||||
OIDC_RP_SCOPES: "openid email"
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://meet.numerique.gouv.fr
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://visio.numerique.gouv.fr
|
||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
|
||||
LOGIN_REDIRECT_URL: https://meet.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://meet.numerique.gouv.fr
|
||||
LOGOUT_REDIRECT_URL: https://meet.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL: https://visio.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://visio.numerique.gouv.fr
|
||||
LOGOUT_REDIRECT_URL: https://visio.numerique.gouv.fr
|
||||
DB_HOST:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
@@ -96,6 +96,9 @@ backend:
|
||||
LIVEKIT_API_URL: https://livekit-preprod.beta.numerique.gouv.fr
|
||||
ANALYTICS_KEY: mwuxTKi8o2xzWH54
|
||||
ALLOW_UNREGISTERED_ROOMS: False
|
||||
FRONTEND_SILENCE_LIVEKIT_DEBUG: False
|
||||
FRONTEND_ANALYTICS: "{'id': 'phc_RPYko028Oqtj0c9exLIWwrlrjLxSdxT0ntW0Lam4iom', 'host': 'https://product.visio.numerique.gouv.fr'}"
|
||||
FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041'}"
|
||||
|
||||
createsuperuser:
|
||||
command:
|
||||
@@ -109,20 +112,39 @@ frontend:
|
||||
image:
|
||||
repository: lasuite/meet-frontend
|
||||
pullPolicy: Always
|
||||
tag: "v0.1.5"
|
||||
tag: "v0.1.6"
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: meet.numerique.gouv.fr
|
||||
host: visio.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
|
||||
ingressAdmin:
|
||||
enabled: true
|
||||
host: meet.numerique.gouv.fr
|
||||
host: visio.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
nginx.ingress.kubernetes.io/auth-signin: https://oauth2-proxy.beta.numerique.gouv.fr/oauth2/start
|
||||
nginx.ingress.kubernetes.io/auth-url: https://oauth2-proxy.beta.numerique.gouv.fr/oauth2/auth
|
||||
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: true
|
||||
host: product.visio.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
nginx.ingress.kubernetes.io/upstream-vhost: eu.i.posthog.com
|
||||
nginx.ingress.kubernetes.io/backend-protocol: https
|
||||
|
||||
ingressAssets:
|
||||
enabled: true
|
||||
host: product.visio.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
nginx.ingress.kubernetes.io/upstream-vhost: eu-assets.i.posthog.com
|
||||
nginx.ingress.kubernetes.io/backend-protocol: https
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
image:
|
||||
repository: lasuite/meet-backend
|
||||
pullPolicy: Always
|
||||
tag: "hackathon"
|
||||
tag: "main"
|
||||
|
||||
backend:
|
||||
migrateJobAnnotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||
envVars:
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS: http://meet-staging.beta.numerique.gouv.fr,https://meet-staging.beta.numerique.gouv.fr
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS: http://visio-staging.beta.numerique.gouv.fr,https://meet-staging.beta.numerique.gouv.fr
|
||||
DJANGO_CONFIGURATION: Production
|
||||
DJANGO_ALLOWED_HOSTS: meet-staging.beta.numerique.gouv.fr
|
||||
DJANGO_ALLOWED_HOSTS: visio-staging.beta.numerique.gouv.fr
|
||||
DJANGO_SECRET_KEY:
|
||||
secretKeyRef:
|
||||
name: backend
|
||||
@@ -42,11 +42,11 @@ backend:
|
||||
key: OIDC_RP_CLIENT_SECRET
|
||||
OIDC_RP_SIGN_ALGO: RS256
|
||||
OIDC_RP_SCOPES: "openid email"
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://meet-staging.beta.numerique.gouv.fr
|
||||
OIDC_REDIRECT_ALLOWED_HOSTS: https://visio-staging.beta.numerique.gouv.fr
|
||||
OIDC_AUTH_REQUEST_EXTRA_PARAMS: "{'acr_values': 'eidas1'}"
|
||||
LOGIN_REDIRECT_URL: https://meet-staging.beta.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://meet-staging.beta.numerique.gouv.fr
|
||||
LOGOUT_REDIRECT_URL: https://meet-staging.beta.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL: https://visio-staging.beta.numerique.gouv.fr
|
||||
LOGIN_REDIRECT_URL_FAILURE: https://visio-staging.beta.numerique.gouv.fr
|
||||
LOGOUT_REDIRECT_URL: https://visio-staging.beta.numerique.gouv.fr
|
||||
DB_HOST:
|
||||
secretKeyRef:
|
||||
name: postgresql.postgres.libre.sh
|
||||
@@ -95,6 +95,8 @@ backend:
|
||||
LIVEKIT_API_URL: https://livekit-staging.beta.numerique.gouv.fr
|
||||
ANALYTICS_KEY: Roi1k6IAc2DEqHB0
|
||||
ALLOW_UNREGISTERED_ROOMS: False
|
||||
FRONTEND_ANALYTICS: "{'id': 'phc_RPYko028Oqtj0c9exLIWwrlrjLxSdxT0ntW0Lam4iom', 'host': 'https://product.visio-staging.beta.numerique.gouv.fr'}"
|
||||
FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041'}"
|
||||
|
||||
createsuperuser:
|
||||
command:
|
||||
@@ -108,20 +110,51 @@ frontend:
|
||||
image:
|
||||
repository: lasuite/meet-frontend
|
||||
pullPolicy: Always
|
||||
tag: "hackathon"
|
||||
tag: "main"
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
host: meet-staging.beta.numerique.gouv.fr
|
||||
host: visio-staging.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
tls:
|
||||
enabled: true
|
||||
additional:
|
||||
- secretName: transitional-tls
|
||||
hosts:
|
||||
- {{ .Values.newDomain }}
|
||||
|
||||
ingressAdmin:
|
||||
enabled: true
|
||||
host: meet-staging.beta.numerique.gouv.fr
|
||||
host: visio-staging.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/auth-signin: https://oauth2-proxy-preprod.beta.numerique.gouv.fr/oauth2/start
|
||||
nginx.ingress.kubernetes.io/auth-url: https://oauth2-proxy-preprod.beta.numerique.gouv.fr/oauth2/auth
|
||||
tls:
|
||||
enabled: true
|
||||
additional:
|
||||
- secretName: transitional-tls
|
||||
hosts:
|
||||
- {{ .Values.newDomain }}
|
||||
|
||||
posthog:
|
||||
ingress:
|
||||
enabled: true
|
||||
host: product.visio-staging.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/upstream-vhost: eu.i.posthog.com
|
||||
nginx.ingress.kubernetes.io/backend-protocol: https
|
||||
|
||||
ingressAssets:
|
||||
enabled: true
|
||||
host: product.visio-staging.beta.numerique.gouv.fr
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/upstream-vhost: eu-assets.i.posthog.com
|
||||
nginx.ingress.kubernetes.io/backend-protocol: https
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
{{ if .Values.addRedirect }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
{{ if .Values.enablePermanentRedirect }}
|
||||
nginx.ingress.kubernetes.io/permanent-redirect: "https://{{ .Values.newDomain }}$request_uri"
|
||||
nginx.ingress.kubernetes.io/permanent-redirect-code: "308"
|
||||
{{ end }}
|
||||
name: temporary-redirect
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: {{ .Values.oldDomain }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: meet-frontend
|
||||
port:
|
||||
number: 80
|
||||
path: /
|
||||
pathType: Prefix
|
||||
- backend:
|
||||
service:
|
||||
name: meet-backend
|
||||
port:
|
||||
number: 80
|
||||
path: /api
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.oldDomain }}
|
||||
secretName: transitional-tls
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: transitional-tls
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
spec:
|
||||
dnsNames:
|
||||
- {{ .Values.newDomain }}
|
||||
- {{ .Values.oldDomain }}
|
||||
issuerRef:
|
||||
group: cert-manager.io
|
||||
kind: ClusterIssuer
|
||||
name: {{ index .Values.ingress.annotations "cert-manager.io/cluster-issuer" }}
|
||||
secretName: transitional-tls
|
||||
usages:
|
||||
- digital signature
|
||||
- key encipherment
|
||||
{{ end }}
|
||||
+31
-22
@@ -1,3 +1,28 @@
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
staging:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
addRedirect: True
|
||||
enablePermanentRedirect: True
|
||||
oldDomain: meet-staging.beta.numerique.gouv.fr
|
||||
newDomain: visio-staging.beta.numerique.gouv.fr
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
production:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
addRedirect: True
|
||||
enablePermanentRedirect: True
|
||||
oldDomain: meet.numerique.gouv.fr
|
||||
newDomain: visio.numerique.gouv.fr
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: registry-1.docker.io/bitnamicharts
|
||||
@@ -36,6 +61,12 @@ releases:
|
||||
chart: ./extra
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.meet.yaml.gotmpl
|
||||
- addRedirect: {{ .Values | get "addRedirect" "False" }}
|
||||
enablePermanentRedirect: {{ .Values | get "enablePermanentRedirect" "False"}}
|
||||
oldDomain: {{ .Values | get "oldDomain" "demo.com" }}
|
||||
newDomain: {{ .Values | get "newDomain" "demo.com" }}
|
||||
|
||||
- name: meet
|
||||
version: {{ .Values.version }}
|
||||
@@ -54,25 +85,3 @@ releases:
|
||||
- env.d/{{ .Environment.Name }}/values.livekit.yaml.gotmpl
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
staging:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
preprod:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
production:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
@@ -157,6 +157,15 @@ Requires top level scope
|
||||
{{ include "meet.fullname" . }}-webrtc
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Full name for the Posthog
|
||||
|
||||
Requires top level scope
|
||||
*/}}
|
||||
{{- define "meet.posthog.fullname" -}}
|
||||
{{ include "meet.fullname" . }}-posthog
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Usage : {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" .) "imageCredentials" .Values.path.to.the.image1) }}
|
||||
*/}}
|
||||
|
||||
@@ -6,8 +6,9 @@ kind: Job
|
||||
metadata:
|
||||
name: {{ $fullName }}-migrate
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
{{- with .Values.backend.migrateJobAnnotations }}
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: Replace=true,Force=true
|
||||
{{- with .Values.backend.migrateJobAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
|
||||
@@ -6,8 +6,9 @@ kind: Job
|
||||
metadata:
|
||||
name: {{ $fullName }}-createsuperuser
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
{{- with .Values.backend.migrateJobAnnotations }}
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-options: Replace=true,Force=true
|
||||
{{- with .Values.backend.migrateJobAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
{{- if .Values.posthog.ingress.enabled -}}
|
||||
{{- $fullName := include "meet.fullname" . -}}
|
||||
{{- if and .Values.posthog.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.posthog.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.posthog.ingress.annotations "kubernetes.io/ingress.class" .Values.posthog.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}-posthog
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.labels" . | nindent 4 }}
|
||||
{{- with .Values.posthog.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.posthog.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.posthog.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.posthog.ingress.tls.enabled }}
|
||||
tls:
|
||||
{{- if .Values.posthog.ingress.host }}
|
||||
- secretName: {{ $fullName }}-posthog-tls
|
||||
hosts:
|
||||
- {{ .Values.posthog.ingress.host | quote }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingress.tls.additional }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- if .Values.posthog.ingress.host }}
|
||||
- host: {{ .Values.posthog.ingress.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ .Values.posthog.ingress.path }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "meet.posthog.fullname" . }}-proxy
|
||||
port:
|
||||
number: {{ .Values.posthog.service.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "meet.posthog.fullname" . }}-proxy
|
||||
servicePort: {{ .Values.posthog.service.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingress.hosts }}
|
||||
- host: {{ . | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $.Values.posthog.ingress.path | quote }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "meet.posthog.fullname" . }}-proxy
|
||||
port:
|
||||
number: {{ $.Values.posthog.service.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "meet.posthog.fullname" . }}-proxy
|
||||
servicePort: {{ $.Values.posthog.service.port }}
|
||||
{{- end }}
|
||||
{{- with $.Values.posthog.assetsService.customBackends }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
{{- if .Values.posthog.ingressAssets.enabled -}}
|
||||
{{- $fullName := include "meet.fullname" . -}}
|
||||
{{- if and .Values.posthog.ingressAssets.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class" .Values.posthog.ingressAssets.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}-posthog-assets
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.labels" . | nindent 4 }}
|
||||
{{- with .Values.posthog.ingressAssets.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.posthog.ingressAssets.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.posthog.ingressAssets.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.posthog.ingressAssets.tls.enabled }}
|
||||
tls:
|
||||
{{- if .Values.posthog.ingressAssets.host }}
|
||||
- secretName: {{ $fullName }}-posthog-tls
|
||||
hosts:
|
||||
- {{ .Values.posthog.ingressAssets.host | quote }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingressAssets.tls.additional }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- if .Values.posthog.ingressAssets.host }}
|
||||
- host: {{ .Values.posthog.ingressAssets.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ .Values.posthog.ingressAssets.path }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "meet.posthog.fullname" . }}-assets-proxy
|
||||
port:
|
||||
number: {{ .Values.posthog.assetsService.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "meet.posthog.fullname" . }}
|
||||
servicePort: {{ .Values.posthog.assetsService.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.posthog.ingressAssets.hosts }}
|
||||
- host: {{ . | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $.Values.posthog.ingressAssets.path | quote }}
|
||||
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
pathType: Prefix
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ include "meet.posthog.fullname" . }}-assets-proxy
|
||||
port:
|
||||
number: {{ $.Values.posthog.assetsService.service.port }}
|
||||
{{- else }}
|
||||
serviceName: {{ include "meet.posthog.fullname" . }}-assets-proxy
|
||||
servicePort: {{ $.Values.posthog.assetsService.service.port }}
|
||||
{{- end }}
|
||||
{{- with $.Values.posthog.assetsService.customBackends }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{{- if .Values.posthog.ingressAssets.enabled -}}
|
||||
{{- $envVars := include "meet.common.env" (list . .Values.posthog) -}}
|
||||
{{- $fullName := include "meet.posthog.fullname" . -}}
|
||||
{{- $component := "posthog" -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $fullName }}-assets-proxy
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml $.Values.posthog.assetsService.annotations | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.posthog.assetsService.type }}
|
||||
externalName: {{ .Values.posthog.assetsService.externalName }}
|
||||
ports:
|
||||
- port: {{ .Values.posthog.assetsService.port }}
|
||||
targetPort: {{ .Values.posthog.assetsService.targetPort }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,24 @@
|
||||
{{- if .Values.posthog.ingress.enabled -}}
|
||||
{{- $envVars := include "meet.common.env" (list . .Values.posthog) -}}
|
||||
{{- $fullName := include "meet.posthog.fullname" . -}}
|
||||
{{- $component := "posthog" -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ $fullName }}-proxy
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml $.Values.posthog.service.annotations | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.posthog.service.type }}
|
||||
externalName: {{ .Values.posthog.service.externalName }}
|
||||
ports:
|
||||
- port: {{ .Values.posthog.service.port }}
|
||||
targetPort: {{ .Values.posthog.service.targetPort }}
|
||||
protocol: TCP
|
||||
name: https
|
||||
selector:
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -3,6 +3,10 @@ kind: Secret
|
||||
metadata:
|
||||
name: backend
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install,pre-upgrade
|
||||
"helm.sh/hook-weight": "-5"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
stringData:
|
||||
DJANGO_SUPERUSER_EMAIL: {{ .Values.djangoSuperUserEmail }}
|
||||
DJANGO_SUPERUSER_PASSWORD: {{ .Values.djangoSuperUserPass }}
|
||||
|
||||
@@ -263,3 +263,45 @@ frontend:
|
||||
|
||||
## @param frontend.extraVolumes Additional volumes to mount on the frontend.
|
||||
extraVolumes: []
|
||||
|
||||
## @section Posthog
|
||||
|
||||
posthog:
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: null
|
||||
host: meet.example.com
|
||||
path: /
|
||||
hosts: [ ]
|
||||
tls:
|
||||
enabled: true
|
||||
additional: [ ]
|
||||
|
||||
customBackends: [ ]
|
||||
annotations: {}
|
||||
|
||||
ingressAssets:
|
||||
enabled: false
|
||||
className: null
|
||||
host: meet.example.com
|
||||
path: /static
|
||||
hosts: [ ]
|
||||
tls:
|
||||
enabled: true
|
||||
additional: [ ]
|
||||
|
||||
customBackends: [ ]
|
||||
annotations: {}
|
||||
|
||||
service:
|
||||
type: ExternalName
|
||||
externalName: eu.i.posthog.com
|
||||
port: 443
|
||||
annotations: {}
|
||||
|
||||
assetsService:
|
||||
type: ExternalName
|
||||
externalName: eu-assets.i.posthog.com
|
||||
port: 443
|
||||
annotations: {}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.5.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
@@ -14,7 +14,7 @@
|
||||
"build": "npm run build-mjml-to-html && npm run build-html-to-plain-text"
|
||||
},
|
||||
"volta": {
|
||||
"node": "16.15.1"
|
||||
"node": "20.17.0"
|
||||
},
|
||||
"repository": "https://github.com/numerique-gouv/meet",
|
||||
"author": "DINUM",
|
||||
|
||||
Reference in New Issue
Block a user