mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-27 20:29:09 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 543ac20d66 | |||
| 1d42dd26ed |
@@ -152,7 +152,7 @@ jobs:
|
|||||||
uses: numerique-gouv/action-trivy-cache@main
|
uses: numerique-gouv/action-trivy-cache@main
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
docker-build-args: '-f src/summary/Dockerfile --target production'
|
docker-build-args: '-f src/summary/Dockerfile --target summary-production'
|
||||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary:${{ github.sha }}'
|
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary:${{ github.sha }}'
|
||||||
docker-context: './src/summary'
|
docker-context: './src/summary'
|
||||||
-
|
-
|
||||||
@@ -161,7 +161,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: ./src/summary
|
context: ./src/summary
|
||||||
file: ./src/summary/Dockerfile
|
file: ./src/summary/Dockerfile
|
||||||
target: production
|
target: summary-production
|
||||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
uses: numerique-gouv/action-trivy-cache@main
|
uses: numerique-gouv/action-trivy-cache@main
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
docker-build-args: '-f src/agents/Dockerfile --target production'
|
docker-build-args: '-f src/agents/Dockerfile --target agents-production'
|
||||||
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-agents:${{ github.sha }}'
|
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-agents:${{ github.sha }}'
|
||||||
docker-context: './src/agents'
|
docker-context: './src/agents'
|
||||||
-
|
-
|
||||||
@@ -200,7 +200,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: ./src/agents
|
context: ./src/agents
|
||||||
file: ./src/agents/Dockerfile
|
file: ./src/agents/Dockerfile
|
||||||
target: production
|
target: agents-production
|
||||||
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- ♻️(frontend) Use React Aria FocusScope for side panel accessibility
|
|
||||||
|
|
||||||
## [1.7.0] - 2026-02-19
|
## [1.7.0] - 2026-02-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ DOCKER_GID = $(shell id -g)
|
|||||||
DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID)
|
DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID)
|
||||||
COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose
|
COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose
|
||||||
COMPOSE_EXEC = $(COMPOSE) exec
|
COMPOSE_EXEC = $(COMPOSE) exec
|
||||||
COMPOSE_EXEC_APP = $(COMPOSE_EXEC) app-dev
|
COMPOSE_EXEC_APP = $(COMPOSE_EXEC) app-development
|
||||||
COMPOSE_RUN = $(COMPOSE) run --rm
|
COMPOSE_RUN = $(COMPOSE) run --rm
|
||||||
COMPOSE_RUN_APP = $(COMPOSE_RUN) app-dev
|
COMPOSE_RUN_APP = $(COMPOSE_RUN) app-development
|
||||||
COMPOSE_RUN_CROWDIN = $(COMPOSE_RUN) crowdin crowdin
|
COMPOSE_RUN_CROWDIN = $(COMPOSE_RUN) crowdin crowdin
|
||||||
WAIT_DB = @$(COMPOSE_RUN) dockerize -wait tcp://$(DB_HOST):$(DB_PORT) -timeout 60s
|
WAIT_DB = @$(COMPOSE_RUN) dockerize -wait tcp://$(DB_HOST):$(DB_PORT) -timeout 60s
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ build: ## build the project containers
|
|||||||
@$(MAKE) build-frontend
|
@$(MAKE) build-frontend
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
|
||||||
build-backend: ## build the app-dev container
|
build-backend: ## build the app-development container
|
||||||
@$(COMPOSE) build app-dev
|
@$(COMPOSE) build app-development
|
||||||
.PHONY: build-backend
|
.PHONY: build-backend
|
||||||
|
|
||||||
|
|
||||||
@@ -107,12 +107,12 @@ down: ## stop and remove containers, networks, images, and volumes
|
|||||||
@$(COMPOSE) down
|
@$(COMPOSE) down
|
||||||
.PHONY: down
|
.PHONY: down
|
||||||
|
|
||||||
logs: ## display app-dev logs (follow mode)
|
logs: ## display app-development logs (follow mode)
|
||||||
@$(COMPOSE) logs -f app-dev
|
@$(COMPOSE) logs -f app-development
|
||||||
.PHONY: logs
|
.PHONY: logs
|
||||||
|
|
||||||
run-backend: ## start only the backend application and all needed services
|
run-backend: ## start only the backend application and all needed services
|
||||||
@$(COMPOSE) up --force-recreate -d celery-dev
|
@$(COMPOSE) up --force-recreate -d celery-development
|
||||||
@echo "Wait for postgresql to be up..."
|
@echo "Wait for postgresql to be up..."
|
||||||
@$(WAIT_DB)
|
@$(WAIT_DB)
|
||||||
.PHONY: run-backend
|
.PHONY: run-backend
|
||||||
@@ -227,14 +227,14 @@ back-i18n-generate: ## create the .pot files used for i18n
|
|||||||
@$(MANAGE) makemessages -a --keep-pot
|
@$(MANAGE) makemessages -a --keep-pot
|
||||||
.PHONY: back-i18n-generate
|
.PHONY: back-i18n-generate
|
||||||
|
|
||||||
shell: ## connect to database shell
|
shell: ## open a Django Python shell
|
||||||
@$(MANAGE) shell #_plus
|
@$(MANAGE) shell #_plus
|
||||||
.PHONY: dbshell
|
.PHONY: shell
|
||||||
|
|
||||||
# -- Database
|
# -- Database
|
||||||
|
|
||||||
dbshell: ## connect to database shell
|
dbshell: ## connect to database shell
|
||||||
docker compose exec app-dev python manage.py dbshell
|
docker compose exec app-development python manage.py dbshell
|
||||||
.PHONY: dbshell
|
.PHONY: dbshell
|
||||||
|
|
||||||
resetdb: FLUSH_ARGS ?=
|
resetdb: FLUSH_ARGS ?=
|
||||||
@@ -348,16 +348,16 @@ build-k8s-cluster: ## build the kubernetes cluster using kind
|
|||||||
|
|
||||||
install-external-secrets: ## install the kubernetes secrets from Vaultwarden
|
install-external-secrets: ## install the kubernetes secrets from Vaultwarden
|
||||||
./bin/install-external-secrets.sh
|
./bin/install-external-secrets.sh
|
||||||
.PHONY: build-k8s-cluster
|
.PHONY: install-external-secrets
|
||||||
|
|
||||||
start-tilt: ## start the kubernetes cluster using kind
|
start-tilt: ## start the kubernetes cluster using kind
|
||||||
tilt up -f ./bin/Tiltfile
|
tilt up -f ./bin/Tiltfile
|
||||||
.PHONY: build-k8s-cluster
|
.PHONY: start-tilt
|
||||||
|
|
||||||
start-tilt-keycloak: ## start the kubernetes cluster using kind, without Pro Connect for authentication, use keycloak
|
start-tilt-keycloak: ## start the kubernetes cluster using kind, without Pro Connect for authentication, use keycloak
|
||||||
DEV_ENV=dev-keycloak tilt up -f ./bin/Tiltfile
|
DEV_ENV=dev-keycloak tilt up -f ./bin/Tiltfile
|
||||||
.PHONY: build-k8s-cluster
|
.PHONY: start-tilt-keycloak
|
||||||
|
|
||||||
start-tilt-dinum: ## start the kubernetes cluster using kind, without Pro Connect for authentication, but with DINUM styles
|
start-tilt-dinum: ## start the kubernetes cluster using kind, without Pro Connect for authentication, but with DINUM styles
|
||||||
DEV_ENV=dev-dinum tilt up -f ./bin/Tiltfile
|
DEV_ENV=dev-dinum tilt up -f ./bin/Tiltfile
|
||||||
.PHONY: build-k8s-cluster
|
.PHONY: start-tilt-dinum
|
||||||
|
|||||||
+2
-2
@@ -59,7 +59,7 @@ docker_build(
|
|||||||
context='../src/summary',
|
context='../src/summary',
|
||||||
dockerfile='../src/summary/Dockerfile',
|
dockerfile='../src/summary/Dockerfile',
|
||||||
only=['.'],
|
only=['.'],
|
||||||
target = 'production',
|
target = 'summary-production',
|
||||||
live_update=[
|
live_update=[
|
||||||
sync('../src/summary', '/app'),
|
sync('../src/summary', '/app'),
|
||||||
]
|
]
|
||||||
@@ -71,7 +71,7 @@ docker_build(
|
|||||||
context='../src/agents',
|
context='../src/agents',
|
||||||
dockerfile='../src/agents/Dockerfile',
|
dockerfile='../src/agents/Dockerfile',
|
||||||
only=['.'],
|
only=['.'],
|
||||||
target = 'production',
|
target = 'agents-production',
|
||||||
live_update=[
|
live_update=[
|
||||||
sync('../src/agents', '/app'),
|
sync('../src/agents', '/app'),
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -89,5 +89,5 @@ function _dc_exec() {
|
|||||||
#
|
#
|
||||||
# ARGS : django's manage.py command arguments
|
# ARGS : django's manage.py command arguments
|
||||||
function _django_manage() {
|
function _django_manage() {
|
||||||
_dc_run "app-dev" python manage.py "$@"
|
_dc_run "app-development" python manage.py "$@"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,5 +4,5 @@ source "$(dirname "${BASH_SOURCE[0]}")/_config.sh"
|
|||||||
|
|
||||||
_dc_run \
|
_dc_run \
|
||||||
-e DJANGO_CONFIGURATION=Test \
|
-e DJANGO_CONFIGURATION=Test \
|
||||||
app-dev \
|
app-development \
|
||||||
pytest "$@"
|
pytest "$@"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/_config.sh"
|
|||||||
|
|
||||||
_dc_run \
|
_dc_run \
|
||||||
-e DJANGO_CONFIGURATION=Test \
|
-e DJANGO_CONFIGURATION=Test \
|
||||||
app-dev \
|
app-development \
|
||||||
python manage.py spectacular \
|
python manage.py spectacular \
|
||||||
--api-version 'v1.0' \
|
--api-version 'v1.0' \
|
||||||
--urlconf 'meet.api_urls' \
|
--urlconf 'meet.api_urls' \
|
||||||
|
|||||||
+14
-14
@@ -55,13 +55,13 @@ services:
|
|||||||
entrypoint: >
|
entrypoint: >
|
||||||
sh -c "
|
sh -c "
|
||||||
/usr/bin/mc alias set meet http://minio:9000 meet password &&
|
/usr/bin/mc alias set meet http://minio:9000 meet password &&
|
||||||
/usr/bin/mc admin config set meet notify_webhook:meet-webhook endpoint='http://app-dev:8000/api/v1.0/recordings/storage-hook/' auth_token='Bearer password' &&
|
/usr/bin/mc admin config set meet notify_webhook:meet-webhook endpoint='http://app-development:8000/api/v1.0/recordings/storage-hook/' auth_token='Bearer password' &&
|
||||||
/usr/bin/mc admin service restart meet --wait --json &&
|
/usr/bin/mc admin service restart meet --wait --json &&
|
||||||
sleep 15 &&
|
sleep 15 &&
|
||||||
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put &&
|
/usr/bin/mc event add meet/meet-media-storage arn:minio:sqs::meet-webhook:webhook --event put &&
|
||||||
exit 0;"
|
exit 0;"
|
||||||
|
|
||||||
app-dev:
|
app-development:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
target: backend-development
|
target: backend-development
|
||||||
@@ -94,7 +94,7 @@ services:
|
|||||||
- resource-server
|
- resource-server
|
||||||
- default
|
- default
|
||||||
|
|
||||||
celery-dev:
|
celery-development:
|
||||||
user: ${DOCKER_USER:-1000}
|
user: ${DOCKER_USER:-1000}
|
||||||
image: meet:backend-development
|
image: meet:backend-development
|
||||||
command: ["celery", "-A", "meet.celery_app", "worker", "-l", "DEBUG"]
|
command: ["celery", "-A", "meet.celery_app", "worker", "-l", "DEBUG"]
|
||||||
@@ -107,9 +107,9 @@ services:
|
|||||||
- ./src/backend:/app
|
- ./src/backend:/app
|
||||||
- ./data/static:/data/static
|
- ./data/static:/data/static
|
||||||
depends_on:
|
depends_on:
|
||||||
- app-dev
|
- app-development
|
||||||
|
|
||||||
app:
|
app-production:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
target: backend-production
|
target: backend-production
|
||||||
@@ -128,7 +128,7 @@ services:
|
|||||||
- livekit
|
- livekit
|
||||||
- minio
|
- minio
|
||||||
|
|
||||||
celery:
|
celery-production:
|
||||||
user: ${DOCKER_USER:-1000}
|
user: ${DOCKER_USER:-1000}
|
||||||
image: meet:backend-production
|
image: meet:backend-production
|
||||||
command: ["celery", "-A", "meet.celery_app", "worker", "-l", "INFO"]
|
command: ["celery", "-A", "meet.celery_app", "worker", "-l", "INFO"]
|
||||||
@@ -138,7 +138,7 @@ services:
|
|||||||
- env.d/development/common
|
- env.d/development/common
|
||||||
- env.d/development/postgresql
|
- env.d/development/postgresql
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app-production
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:1.25
|
image: nginx:1.25
|
||||||
@@ -161,7 +161,7 @@ services:
|
|||||||
args:
|
args:
|
||||||
VITE_API_BASE_URL: "http://localhost:8071"
|
VITE_API_BASE_URL: "http://localhost:8071"
|
||||||
VITE_APP_TITLE: "LaSuite Meet"
|
VITE_APP_TITLE: "LaSuite Meet"
|
||||||
image: meet:frontend-development
|
image: meet:frontend-production
|
||||||
ports:
|
ports:
|
||||||
- "3000:8080"
|
- "3000:8080"
|
||||||
|
|
||||||
@@ -249,10 +249,10 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
|
|
||||||
app-summary-dev:
|
app-summary-development:
|
||||||
build:
|
build:
|
||||||
context: src/summary
|
context: src/summary
|
||||||
target: development
|
target: summary-development
|
||||||
args:
|
args:
|
||||||
DOCKER_USER: ${DOCKER_USER:-1000}
|
DOCKER_USER: ${DOCKER_USER:-1000}
|
||||||
user: ${DOCKER_USER:-1000}
|
user: ${DOCKER_USER:-1000}
|
||||||
@@ -270,7 +270,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./src/summary
|
context: ./src/summary
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: production
|
target: summary-production
|
||||||
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug -Q transcribe-queue
|
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug -Q transcribe-queue
|
||||||
env_file:
|
env_file:
|
||||||
- env.d/development/summary
|
- env.d/development/summary
|
||||||
@@ -278,7 +278,7 @@ services:
|
|||||||
- ./src/summary:/app
|
- ./src/summary:/app
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-summary
|
- redis-summary
|
||||||
- app-summary-dev
|
- app-summary-development
|
||||||
- minio
|
- minio
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
@@ -290,7 +290,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./src/summary
|
context: ./src/summary
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: production
|
target: summary-production
|
||||||
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug -Q summarize-queue
|
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug -Q summarize-queue
|
||||||
env_file:
|
env_file:
|
||||||
- env.d/development/summary
|
- env.d/development/summary
|
||||||
@@ -298,7 +298,7 @@ services:
|
|||||||
- ./src/summary:/app
|
- ./src/summary:/app
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-summary
|
- redis-summary
|
||||||
- app-summary-dev
|
- app-summary-development
|
||||||
- minio
|
- minio
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ keys:
|
|||||||
webhook:
|
webhook:
|
||||||
api_key: devkey
|
api_key: devkey
|
||||||
urls:
|
urls:
|
||||||
- http://app-dev:8000/api/v1.0/rooms/webhooks-livekit/
|
- http://app-development:8000/api/v1.0/rooms/webhooks-livekit/
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ ALLOW_UNREGISTERED_ROOMS=False
|
|||||||
RECORDING_ENABLE=True
|
RECORDING_ENABLE=True
|
||||||
RECORDING_STORAGE_EVENT_ENABLE=True
|
RECORDING_STORAGE_EVENT_ENABLE=True
|
||||||
RECORDING_STORAGE_EVENT_TOKEN=password
|
RECORDING_STORAGE_EVENT_TOKEN=password
|
||||||
SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v1/tasks/
|
SUMMARY_SERVICE_ENDPOINT=http://app-summary-development:8000/api/v1/tasks/
|
||||||
SUMMARY_SERVICE_API_TOKEN=password
|
SUMMARY_SERVICE_API_TOKEN=password
|
||||||
RECORDING_DOWNLOAD_BASE_URL=http://localhost:3000/recording
|
RECORDING_DOWNLOAD_BASE_URL=http://localhost:3000/recording
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
APP_NAME="meet-app-summary-dev"
|
APP_NAME="meet-app-summary-development"
|
||||||
APP_API_TOKEN="password"
|
APP_API_TOKEN="password"
|
||||||
|
|
||||||
AWS_STORAGE_BUCKET_NAME="http://meet-media-storage"
|
AWS_STORAGE_BUCKET_NAME="http://meet-media-storage"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ COPY pyproject.toml .
|
|||||||
RUN mkdir /install && \
|
RUN mkdir /install && \
|
||||||
pip install --prefix=/install .
|
pip install --prefix=/install .
|
||||||
|
|
||||||
FROM base AS production
|
FROM base AS agents-production
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
import { ParticipantsList } from './controls/Participants/ParticipantsList'
|
||||||
import { useSidePanel } from '../hooks/useSidePanel'
|
import { useSidePanel } from '../hooks/useSidePanel'
|
||||||
import { ReactNode, useEffect, useRef } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { Chat } from '../prefabs/Chat'
|
import { Chat } from '../prefabs/Chat'
|
||||||
import { Effects } from './effects/Effects'
|
import { Effects } from './effects/Effects'
|
||||||
import { Admin } from './Admin'
|
import { Admin } from './Admin'
|
||||||
@@ -15,11 +15,9 @@ import { Tools } from './Tools'
|
|||||||
import { Info } from './Info'
|
import { Info } from './Info'
|
||||||
import { HStack } from '@/styled-system/jsx'
|
import { HStack } from '@/styled-system/jsx'
|
||||||
|
|
||||||
const SIDE_PANEL_HEADING_ID = 'side-panel-heading'
|
|
||||||
const SIDE_PANEL_CLOSE_ID = 'side-panel-close'
|
|
||||||
|
|
||||||
type StyledSidePanelProps = {
|
type StyledSidePanelProps = {
|
||||||
title: string
|
title: string
|
||||||
|
ariaLabel: string
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
isClosed: boolean
|
isClosed: boolean
|
||||||
@@ -31,6 +29,7 @@ type StyledSidePanelProps = {
|
|||||||
|
|
||||||
const StyledSidePanel = ({
|
const StyledSidePanel = ({
|
||||||
title,
|
title,
|
||||||
|
ariaLabel,
|
||||||
children,
|
children,
|
||||||
onClose,
|
onClose,
|
||||||
isClosed,
|
isClosed,
|
||||||
@@ -39,14 +38,7 @@ const StyledSidePanel = ({
|
|||||||
onBack,
|
onBack,
|
||||||
backButtonLabel,
|
backButtonLabel,
|
||||||
}: StyledSidePanelProps) => (
|
}: StyledSidePanelProps) => (
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- role="dialog" makes this interactive
|
|
||||||
<aside
|
<aside
|
||||||
role="dialog"
|
|
||||||
aria-labelledby={!isClosed ? SIDE_PANEL_HEADING_ID : undefined}
|
|
||||||
aria-hidden={isClosed || undefined}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Escape') onClose()
|
|
||||||
}}
|
|
||||||
className={css({
|
className={css({
|
||||||
borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
@@ -71,57 +63,57 @@ const StyledSidePanel = ({
|
|||||||
style={{
|
style={{
|
||||||
transform: isClosed ? 'translateX(calc(360px + 1.5rem))' : 'none',
|
transform: isClosed ? 'translateX(calc(360px + 1.5rem))' : 'none',
|
||||||
}}
|
}}
|
||||||
|
aria-hidden={isClosed}
|
||||||
|
aria-label={ariaLabel}
|
||||||
>
|
>
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
{isSubmenu && (
|
{isSubmenu && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondaryText"
|
variant="secondaryText"
|
||||||
size="sm"
|
size="sm"
|
||||||
square
|
square
|
||||||
className={css({ marginRight: '0.5rem', marginLeft: '1rem' })}
|
className={css({ marginRight: '0.5rem', marginLeft: '1rem' })}
|
||||||
aria-label={backButtonLabel}
|
aria-label={backButtonLabel}
|
||||||
onPress={onBack}
|
onPress={onBack}
|
||||||
>
|
|
||||||
<RiArrowLeftLine size={20} aria-hidden="true" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Heading
|
|
||||||
id={SIDE_PANEL_HEADING_ID}
|
|
||||||
slot="title"
|
|
||||||
level={1}
|
|
||||||
className={text({ variant: 'h2' })}
|
|
||||||
style={{
|
|
||||||
paddingLeft: isSubmenu ? 0 : '1.5rem',
|
|
||||||
paddingTop: '1rem',
|
|
||||||
display: isClosed ? 'none' : 'flex',
|
|
||||||
justifyContent: 'start',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{title}
|
<RiArrowLeftLine size={20} aria-hidden="true" />
|
||||||
</Heading>
|
</Button>
|
||||||
</HStack>
|
)}
|
||||||
<Div
|
<Heading
|
||||||
position="absolute"
|
slot="title"
|
||||||
top="5"
|
level={1}
|
||||||
right="5"
|
className={text({ variant: 'h2' })}
|
||||||
style={{
|
style={{
|
||||||
display: isClosed ? 'none' : undefined,
|
paddingLeft: isSubmenu ? 0 : '1.5rem',
|
||||||
|
paddingTop: '1rem',
|
||||||
|
display: isClosed ? 'none' : 'flex',
|
||||||
|
justifyContent: 'start',
|
||||||
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
{title}
|
||||||
id={SIDE_PANEL_CLOSE_ID}
|
</Heading>
|
||||||
invisible
|
</HStack>
|
||||||
variant="tertiaryText"
|
<Div
|
||||||
size="xs"
|
position="absolute"
|
||||||
onPress={onClose}
|
top="5"
|
||||||
aria-label={closeButtonTooltip}
|
right="5"
|
||||||
tooltip={closeButtonTooltip}
|
style={{
|
||||||
>
|
display: isClosed ? 'none' : undefined,
|
||||||
<RiCloseLine />
|
}}
|
||||||
</Button>
|
>
|
||||||
</Div>
|
<Button
|
||||||
{children}
|
invisible
|
||||||
|
variant="tertiaryText"
|
||||||
|
size="xs"
|
||||||
|
onPress={onClose}
|
||||||
|
aria-label={closeButtonTooltip}
|
||||||
|
tooltip={closeButtonTooltip}
|
||||||
|
>
|
||||||
|
<RiCloseLine />
|
||||||
|
</Button>
|
||||||
|
</Div>
|
||||||
|
{children}
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -143,7 +135,6 @@ const Panel = ({ isOpen, keepAlive = false, children }: PanelProps) => (
|
|||||||
{keepAlive || isOpen ? children : null}
|
{keepAlive || isOpen ? children : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SidePanel = () => {
|
export const SidePanel = () => {
|
||||||
const {
|
const {
|
||||||
activePanelId,
|
activePanelId,
|
||||||
@@ -159,51 +150,14 @@ export const SidePanel = () => {
|
|||||||
} = useSidePanel()
|
} = useSidePanel()
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
|
||||||
|
|
||||||
const triggerRef = useRef<HTMLElement | null>(null)
|
|
||||||
|
|
||||||
// The aside stays mounted (CSS slide + keepAlive), so we manually handle
|
|
||||||
// auto-focus on open and restore focus on close (via handleClose).
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSidePanelOpen) return
|
|
||||||
const active = document.activeElement as HTMLElement
|
|
||||||
// Menu items render as DIVs that unmount when the menu closes — resolve to the menu trigger
|
|
||||||
triggerRef.current =
|
|
||||||
active?.tagName === 'DIV'
|
|
||||||
? (document.querySelector<HTMLElement>('#room-options-trigger') ??
|
|
||||||
active)
|
|
||||||
: active
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const closeBtn = document.getElementById(SIDE_PANEL_CLOSE_ID)
|
|
||||||
// Skip if a child panel already moved focus inside (e.g. Chat input)
|
|
||||||
if (closeBtn?.closest('aside')?.contains(document.activeElement)) return
|
|
||||||
closeBtn?.focus({ preventScroll: true })
|
|
||||||
})
|
|
||||||
}, [isSidePanelOpen])
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
const trigger = triggerRef.current
|
|
||||||
triggerRef.current = null
|
|
||||||
layoutStore.activePanelId = null
|
|
||||||
layoutStore.activeSubPanelId = null
|
|
||||||
// Double RAF: first lets React re-render, second lets FocusScope release containment
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (trigger?.isConnected) {
|
|
||||||
trigger.focus({ preventScroll: true })
|
|
||||||
} else {
|
|
||||||
document
|
|
||||||
.querySelector<HTMLElement>('#room-options-trigger')
|
|
||||||
?.focus({ preventScroll: true })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSidePanel
|
<StyledSidePanel
|
||||||
title={t(`heading.${activeSubPanelId || activePanelId}`)}
|
title={t(`heading.${activeSubPanelId || activePanelId}`)}
|
||||||
onClose={handleClose}
|
ariaLabel={t('ariaLabel')}
|
||||||
|
onClose={() => {
|
||||||
|
layoutStore.activePanelId = null
|
||||||
|
layoutStore.activeSubPanelId = null
|
||||||
|
}}
|
||||||
closeButtonTooltip={t('closeButton', {
|
closeButtonTooltip={t('closeButton', {
|
||||||
content: t(`content.${activeSubPanelId || activePanelId}`),
|
content: t(`content.${activeSubPanelId || activePanelId}`),
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button as RACButton } from 'react-aria-components'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { SubPanelId, useSidePanel } from '../hooks/useSidePanel'
|
import { SubPanelId, useSidePanel } from '../hooks/useSidePanel'
|
||||||
|
import { useRestoreFocus } from '@/hooks/useRestoreFocus'
|
||||||
import {
|
import {
|
||||||
useIsRecordingModeEnabled,
|
useIsRecordingModeEnabled,
|
||||||
RecordingMode,
|
RecordingMode,
|
||||||
@@ -94,10 +95,26 @@ const ToolButton = ({
|
|||||||
|
|
||||||
export const Tools = () => {
|
export const Tools = () => {
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
const { openTranscript, openScreenRecording, activeSubPanelId } =
|
const { openTranscript, openScreenRecording, activeSubPanelId, isToolsOpen } =
|
||||||
useSidePanel()
|
useSidePanel()
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
||||||
|
|
||||||
|
// Restore focus to the element that opened the Tools panel
|
||||||
|
// following the same pattern as Chat.
|
||||||
|
useRestoreFocus(isToolsOpen, {
|
||||||
|
// If the active element is a MenuItem (DIV) that will be unmounted when the menu closes,
|
||||||
|
// find the "more options" button ("Plus d'options") that opened the menu
|
||||||
|
resolveTrigger: (activeEl) => {
|
||||||
|
if (activeEl?.tagName === 'DIV') {
|
||||||
|
return document.querySelector<HTMLElement>('#room-options-trigger')
|
||||||
|
}
|
||||||
|
// For direct button clicks (e.g. "Plus d'outils"), use the active element as is
|
||||||
|
return activeEl
|
||||||
|
},
|
||||||
|
restoreFocusRaf: true,
|
||||||
|
preventScroll: true,
|
||||||
|
})
|
||||||
|
|
||||||
const isTranscriptEnabled = useIsRecordingModeEnabled(
|
const isTranscriptEnabled = useIsRecordingModeEnabled(
|
||||||
RecordingMode.Transcript
|
RecordingMode.Transcript
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -80,12 +80,10 @@ export const ChatInput = ({
|
|||||||
<TextArea
|
<TextArea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key !== 'Escape') e.stopPropagation()
|
e.stopPropagation()
|
||||||
submitOnEnter(e)
|
submitOnEnter(e)
|
||||||
}}
|
}}
|
||||||
onKeyUp={(e) => {
|
onKeyUp={(e) => e.stopPropagation()}
|
||||||
if (e.key !== 'Escape') e.stopPropagation()
|
|
||||||
}}
|
|
||||||
placeholder={t('textArea.placeholder')}
|
placeholder={t('textArea.placeholder')}
|
||||||
value={text}
|
value={text}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { ChatEntry } from '../components/chat/Entry'
|
|||||||
import { useSidePanel } from '../hooks/useSidePanel'
|
import { useSidePanel } from '../hooks/useSidePanel'
|
||||||
import { LocalParticipant, RemoteParticipant, RoomEvent } from 'livekit-client'
|
import { LocalParticipant, RemoteParticipant, RoomEvent } from 'livekit-client'
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
|
import { useRestoreFocus } from '@/hooks/useRestoreFocus'
|
||||||
|
|
||||||
export interface ChatProps
|
export interface ChatProps
|
||||||
extends React.HTMLAttributes<HTMLDivElement>, ChatOptions {}
|
extends React.HTMLAttributes<HTMLDivElement>, ChatOptions {}
|
||||||
@@ -35,12 +36,18 @@ export function Chat({ ...props }: ChatProps) {
|
|||||||
const { isChatOpen } = useSidePanel()
|
const { isChatOpen } = useSidePanel()
|
||||||
const chatSnap = useSnapshot(chatStore)
|
const chatSnap = useSnapshot(chatStore)
|
||||||
|
|
||||||
React.useEffect(() => {
|
// Keep track of the element that opened the chat so we can restore focus
|
||||||
if (!isChatOpen) return
|
// when the chat panel is closed.
|
||||||
requestAnimationFrame(() => {
|
useRestoreFocus(isChatOpen, {
|
||||||
inputRef.current?.focus({ preventScroll: true })
|
// Avoid layout "jump" during the side panel slide-in animation.
|
||||||
})
|
// Focusing can trigger scroll into view; preventScroll keeps the animation smooth.
|
||||||
}, [isChatOpen])
|
onOpened: () => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
inputRef.current?.focus({ preventScroll: true })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
preventScroll: true,
|
||||||
|
})
|
||||||
|
|
||||||
// Use useParticipants hook to trigger a re-render when the participant list changes.
|
// Use useParticipants hook to trigger a re-render when the participant list changes.
|
||||||
const participants = useParticipants()
|
const participants = useParticipants()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ COPY pyproject.toml .
|
|||||||
|
|
||||||
RUN pip3 install --no-cache-dir .
|
RUN pip3 install --no-cache-dir .
|
||||||
|
|
||||||
FROM base AS development
|
FROM base AS summary-development
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -24,7 +24,7 @@ RUN pip3 install --no-cache-dir -e ".[dev]" || pip3 install --no-cache-dir -e .
|
|||||||
|
|
||||||
CMD ["uvicorn", "summary.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
CMD ["uvicorn", "summary.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||||
|
|
||||||
FROM base AS production
|
FROM base AS summary-production
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user