Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6593e449e2 | |||
| de6b8e1e22 | |||
| 7ddf9070aa |
@@ -212,7 +212,7 @@ class LobbyService:
|
||||
|
||||
try:
|
||||
utils.notify_participants(
|
||||
room_name=str(room_id),
|
||||
room_name=room_id,
|
||||
notification_data={
|
||||
"type": settings.LOBBY_NOTIFICATION_TYPE,
|
||||
},
|
||||
|
||||
@@ -442,7 +442,7 @@ def test_enter_success(
|
||||
timeout=settings.LOBBY_WAITING_TIMEOUT,
|
||||
)
|
||||
mock_notify.assert_called_once_with(
|
||||
room_name=str(room.id), notification_data={"type": "participantWaiting"}
|
||||
room_name=room.id, notification_data={"type": "participantWaiting"}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "0.1.30"
|
||||
version = "0.1.29"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
Generated
+2
-54
@@ -1,19 +1,18 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meet",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.9.13",
|
||||
"@livekit/components-styles": "1.1.6",
|
||||
"@livekit/track-processors": "0.5.7",
|
||||
"@pandacss/preset-panda": "0.54.0",
|
||||
"@react-aria/toast": "3.0.5",
|
||||
"@react-hook/size": "2.1.2",
|
||||
"@remixicon/react": "4.6.0",
|
||||
"@tanstack/react-query": "5.81.5",
|
||||
"@timephy/rnnoise-wasm": "1.0.0",
|
||||
@@ -1211,12 +1210,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@juggle/resize-observer": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@livekit/components-core": {
|
||||
"version": "0.12.8",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.8.tgz",
|
||||
@@ -2708,51 +2701,6 @@
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/latest": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz",
|
||||
"integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/passive-layout-effect": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz",
|
||||
"integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/resize-observer": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-1.2.6.tgz",
|
||||
"integrity": "sha512-DlBXtLSW0DqYYTW3Ft1/GQFZlTdKY5VAFIC4+km6IK5NiPPDFchGbEJm1j6pSgMqPRHbUQgHJX7RaR76ic1LWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@juggle/resize-observer": "^3.3.1",
|
||||
"@react-hook/latest": "^1.0.2",
|
||||
"@react-hook/passive-layout-effect": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/size": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/size/-/size-2.1.2.tgz",
|
||||
"integrity": "sha512-BmE5asyRDxSuQ9p14FUKJ0iBRgV9cROjqNG9jT/EjCM+xHha1HVqbPoT+14FQg1K7xIydabClCibUY4+1tw/iw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-hook/passive-layout-effect": "^1.2.0",
|
||||
"@react-hook/resize-observer": "^1.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/autocomplete": {
|
||||
"version": "3.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/autocomplete/-/autocomplete-3.0.0-beta.2.tgz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
@@ -18,7 +18,6 @@
|
||||
"@livekit/track-processors": "0.5.7",
|
||||
"@pandacss/preset-panda": "0.54.0",
|
||||
"@react-aria/toast": "3.0.5",
|
||||
"@react-hook/size": "2.1.2",
|
||||
"@remixicon/react": "4.6.0",
|
||||
"@tanstack/react-query": "5.81.5",
|
||||
"@timephy/rnnoise-wasm": "1.0.0",
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
MediaDeviceFailure,
|
||||
Room,
|
||||
RoomOptions,
|
||||
supportsAdaptiveStream,
|
||||
supportsDynacast,
|
||||
} from 'livekit-client'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
import { queryClient } from '@/api/queryClient'
|
||||
@@ -76,8 +74,8 @@ export const Conference = ({
|
||||
|
||||
const roomOptions = useMemo((): RoomOptions => {
|
||||
return {
|
||||
adaptiveStream: supportsAdaptiveStream(),
|
||||
dynacast: supportsDynacast(),
|
||||
adaptiveStream: true,
|
||||
dynacast: true,
|
||||
publishDefaults: {
|
||||
videoCodec: 'vp9',
|
||||
},
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useMemo, useRef } from 'react'
|
||||
import { ScreenSharePreferenceStore } from '@/stores/ScreenSharePreferences'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useLocalParticipant } from '@livekit/components-react'
|
||||
import { useSize } from '../hooks/useResizeObserver'
|
||||
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSize from '@react-hook/size'
|
||||
|
||||
export const FullScreenShareWarning = ({
|
||||
trackReference,
|
||||
@@ -16,7 +16,7 @@ export const FullScreenShareWarning = ({
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'fullScreenWarning' })
|
||||
|
||||
const warningContainerRef = useRef<HTMLDivElement>(null)
|
||||
const containerWidth = useSize(warningContainerRef)[0]
|
||||
const { width: containerWidth } = useSize(warningContainerRef)
|
||||
const { localParticipant } = useLocalParticipant()
|
||||
const screenSharePreferences = useSnapshot(ScreenSharePreferenceStore)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { styled } from '@/styled-system/jsx'
|
||||
import { Avatar } from '@/components/Avatar'
|
||||
import { useIsSpeaking } from '@livekit/components-react'
|
||||
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
|
||||
import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import useSize from '@react-hook/size'
|
||||
|
||||
const StyledParticipantPlaceHolder = styled('div', {
|
||||
base: {
|
||||
@@ -28,7 +28,7 @@ export const ParticipantPlaceholder = ({
|
||||
const participantColor = getParticipantColor(participant)
|
||||
|
||||
const placeholderEl = useRef<HTMLDivElement>(null)
|
||||
const [width, height] = useSize(placeholderEl)
|
||||
const { width, height } = useSize(placeholderEl)
|
||||
|
||||
const minDimension = Math.min(width, height)
|
||||
const avatarSize = useMemo(
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { createInteractingObservable } from '@livekit/components-core'
|
||||
import { usePagination } from '@livekit/components-react'
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
|
||||
export interface PaginationControlProps
|
||||
extends Pick<
|
||||
ReturnType<typeof usePagination>,
|
||||
'totalPageCount' | 'nextPage' | 'prevPage' | 'currentPage'
|
||||
> {
|
||||
/** Reference to an HTML element that holds the pages, while interacting (`mouseover`)
|
||||
* with it, the pagination controls will appear for a while. */
|
||||
pagesContainer?: React.RefObject<HTMLElement>
|
||||
}
|
||||
|
||||
export function PaginationControl({
|
||||
totalPageCount,
|
||||
nextPage,
|
||||
prevPage,
|
||||
currentPage,
|
||||
pagesContainer: connectedElement,
|
||||
}: PaginationControlProps) {
|
||||
const [interactive, setInteractive] = React.useState(false)
|
||||
React.useEffect(() => {
|
||||
let subscription:
|
||||
| ReturnType<ReturnType<typeof createInteractingObservable>['subscribe']>
|
||||
| undefined
|
||||
if (connectedElement) {
|
||||
subscription = createInteractingObservable(
|
||||
connectedElement.current,
|
||||
2000
|
||||
).subscribe(setInteractive)
|
||||
}
|
||||
return () => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
}, [connectedElement])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="lk-pagination-control"
|
||||
data-lk-user-interaction={interactive}
|
||||
>
|
||||
<button className="lk-button" onClick={prevPage}>
|
||||
<RiArrowLeftSLine />
|
||||
</button>
|
||||
<span className="lk-pagination-count">{`${currentPage} of ${totalPageCount}`}</span>
|
||||
<button className="lk-button" onClick={nextPage}>
|
||||
<RiArrowRightSLine />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export interface PaginationIndicatorProps {
|
||||
totalPageCount: number
|
||||
currentPage: number
|
||||
}
|
||||
|
||||
export const PaginationIndicator: (
|
||||
props: PaginationIndicatorProps & React.RefAttributes<HTMLDivElement>
|
||||
) => React.ReactNode = /* @__PURE__ */ React.forwardRef<
|
||||
HTMLDivElement,
|
||||
PaginationIndicatorProps
|
||||
>(function PaginationIndicator(
|
||||
{ totalPageCount, currentPage }: PaginationIndicatorProps,
|
||||
ref
|
||||
) {
|
||||
const bubbles = new Array(totalPageCount).fill('').map((_, index) => {
|
||||
if (index + 1 === currentPage) {
|
||||
return <span data-lk-active key={index} />
|
||||
} else {
|
||||
return <span key={index} />
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={ref} className="lk-pagination-indicator">
|
||||
{bubbles}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -1,92 +0,0 @@
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import { getScrollBarWidth } from '@livekit/components-core'
|
||||
import * as React from 'react'
|
||||
import { TrackLoop, useVisualStableUpdate } from '@livekit/components-react'
|
||||
import useSize from '@react-hook/size'
|
||||
|
||||
const MIN_HEIGHT = 130
|
||||
const MIN_WIDTH = 140
|
||||
const MIN_VISIBLE_TILES = 1
|
||||
const ASPECT_RATIO = 16 / 10
|
||||
const ASPECT_RATIO_INVERT = (1 - ASPECT_RATIO) * -1
|
||||
|
||||
/** @public */
|
||||
export interface CarouselLayoutProps
|
||||
extends React.HTMLAttributes<HTMLMediaElement> {
|
||||
tracks: TrackReferenceOrPlaceholder[]
|
||||
children: React.ReactNode
|
||||
/** Place the tiles vertically or horizontally next to each other.
|
||||
* If undefined orientation is guessed by the dimensions of the container. */
|
||||
orientation?: 'vertical' | 'horizontal'
|
||||
}
|
||||
|
||||
/**
|
||||
* The `CarouselLayout` component displays a list of tracks in a scroll container.
|
||||
* It will display as many tiles as possible and overflow the rest.
|
||||
* @remarks
|
||||
* To ensure visual stability when tiles are reordered due to track updates,
|
||||
* the component uses the `useVisualStableUpdate` hook.
|
||||
* @example
|
||||
* ```tsx
|
||||
* const tracks = useTracks([Track.Source.Camera]);
|
||||
* <CarouselLayout tracks={tracks}>
|
||||
* <ParticipantTile />
|
||||
* </CarouselLayout>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function CarouselLayout({
|
||||
tracks,
|
||||
orientation,
|
||||
...props
|
||||
}: CarouselLayoutProps) {
|
||||
const asideEl = React.useRef<HTMLDivElement>(null)
|
||||
const [prevTiles, setPrevTiles] = React.useState(0)
|
||||
const [width, height] = useSize(asideEl)
|
||||
const carouselOrientation = orientation
|
||||
? orientation
|
||||
: height >= width
|
||||
? 'vertical'
|
||||
: 'horizontal'
|
||||
|
||||
const tileSpan =
|
||||
carouselOrientation === 'vertical'
|
||||
? Math.max(width * ASPECT_RATIO_INVERT, MIN_HEIGHT)
|
||||
: Math.max(height * ASPECT_RATIO, MIN_WIDTH)
|
||||
const scrollBarWidth = getScrollBarWidth()
|
||||
|
||||
const tilesThatFit =
|
||||
carouselOrientation === 'vertical'
|
||||
? Math.max((height - scrollBarWidth) / tileSpan, MIN_VISIBLE_TILES)
|
||||
: Math.max((width - scrollBarWidth) / tileSpan, MIN_VISIBLE_TILES)
|
||||
|
||||
let maxVisibleTiles = Math.round(tilesThatFit)
|
||||
if (Math.abs(tilesThatFit - prevTiles) < 0.5) {
|
||||
maxVisibleTiles = Math.round(prevTiles)
|
||||
} else if (prevTiles !== tilesThatFit) {
|
||||
setPrevTiles(tilesThatFit)
|
||||
}
|
||||
|
||||
const sortedTiles = useVisualStableUpdate(tracks, maxVisibleTiles)
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (asideEl.current) {
|
||||
asideEl.current.dataset.lkOrientation = carouselOrientation
|
||||
asideEl.current.style.setProperty(
|
||||
'--lk-max-visible-tiles',
|
||||
maxVisibleTiles.toString()
|
||||
)
|
||||
}
|
||||
}, [maxVisibleTiles, carouselOrientation])
|
||||
|
||||
return (
|
||||
<aside
|
||||
key={carouselOrientation}
|
||||
className="lk-carousel"
|
||||
ref={asideEl}
|
||||
{...props}
|
||||
>
|
||||
<TrackLoop tracks={sortedTiles}>{props.children}</TrackLoop>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import {
|
||||
TrackLoop,
|
||||
usePagination,
|
||||
UseParticipantsOptions,
|
||||
useSwipe,
|
||||
} from '@livekit/components-react'
|
||||
import { mergeProps } from '@/utils/mergeProps'
|
||||
import { PaginationIndicator } from '../controls/PaginationIndicator'
|
||||
import { useGridLayout } from '../../hooks/useGridLayout'
|
||||
import { PaginationControl } from '../controls/PaginationControl'
|
||||
|
||||
/** @public */
|
||||
export interface GridLayoutProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
Pick<UseParticipantsOptions, 'updateOnlyOn'> {
|
||||
children: React.ReactNode
|
||||
tracks: TrackReferenceOrPlaceholder[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The `GridLayout` component displays the nested participants in a grid where every participants has the same size.
|
||||
* It also supports pagination if there are more participants than the grid can display.
|
||||
* @remarks
|
||||
* To ensure visual stability when tiles are reordered due to track updates,
|
||||
* the component uses the `useVisualStableUpdate` hook.
|
||||
* @example
|
||||
* ```tsx
|
||||
* <LiveKitRoom>
|
||||
* <GridLayout tracks={tracks}>
|
||||
* <ParticipantTile />
|
||||
* </GridLayout>
|
||||
* <LiveKitRoom>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function GridLayout({ tracks, ...props }: GridLayoutProps) {
|
||||
const gridEl = React.createRef<HTMLDivElement>()
|
||||
|
||||
const elementProps = React.useMemo(
|
||||
() => mergeProps(props, { className: 'lk-grid-layout' }),
|
||||
[props]
|
||||
)
|
||||
const { layout } = useGridLayout(gridEl, tracks.length)
|
||||
const pagination = usePagination(layout.maxTiles, tracks)
|
||||
|
||||
useSwipe(gridEl, {
|
||||
onLeftSwipe: pagination.nextPage,
|
||||
onRightSwipe: pagination.prevPage,
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={gridEl}
|
||||
data-lk-pagination={pagination.totalPageCount > 1}
|
||||
{...elementProps}
|
||||
>
|
||||
<TrackLoop tracks={pagination.tracks}>{props.children}</TrackLoop>
|
||||
{tracks.length > layout.maxTiles && (
|
||||
<>
|
||||
<PaginationIndicator
|
||||
totalPageCount={pagination.totalPageCount}
|
||||
currentPage={pagination.currentPage}
|
||||
/>
|
||||
<PaginationControl pagesContainer={gridEl} {...pagination} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { GRID_LAYOUTS, selectGridLayout } from '@livekit/components-core'
|
||||
import type {
|
||||
GridLayoutDefinition,
|
||||
GridLayoutInfo,
|
||||
} from '@livekit/components-core'
|
||||
import * as React from 'react'
|
||||
import useSize from '@react-hook/size'
|
||||
|
||||
/**
|
||||
* The `useGridLayout` hook tries to select the best layout to fit all tiles.
|
||||
* If the available screen space is not enough, it will reduce the number of maximum visible
|
||||
* tiles and select a layout that still works visually within the given limitations.
|
||||
* As the order of tiles changes over time, the hook tries to keep visual updates to a minimum
|
||||
* while trying to display important tiles such as speaking participants or screen shares.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { layout } = useGridLayout(gridElement, trackCount);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function useGridLayout(
|
||||
/** HTML element that contains the grid. */
|
||||
gridElement: React.RefObject<HTMLDivElement>,
|
||||
/** Count of tracks that should get layed out */
|
||||
trackCount: number,
|
||||
options: {
|
||||
gridLayouts?: GridLayoutDefinition[]
|
||||
} = {}
|
||||
): { layout: GridLayoutInfo; containerWidth: number; containerHeight: number } {
|
||||
const gridLayouts = options.gridLayouts ?? GRID_LAYOUTS
|
||||
const [width, height] = useSize(gridElement)
|
||||
const layout = selectGridLayout(gridLayouts, trackCount, width, height)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (gridElement.current && layout) {
|
||||
gridElement.current.style.setProperty(
|
||||
'--lk-col-count',
|
||||
layout?.columns.toString()
|
||||
)
|
||||
gridElement.current.style.setProperty(
|
||||
'--lk-row-count',
|
||||
layout?.rows.toString()
|
||||
)
|
||||
}
|
||||
}, [gridElement, layout])
|
||||
|
||||
return {
|
||||
layout,
|
||||
containerWidth: width,
|
||||
containerHeight: height,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import * as React from 'react'
|
||||
|
||||
const useLatest = <T>(current: T) => {
|
||||
const storedValue = React.useRef(current)
|
||||
React.useEffect(() => {
|
||||
storedValue.current = current
|
||||
})
|
||||
return storedValue
|
||||
}
|
||||
|
||||
/**
|
||||
* A React hook that fires a callback whenever ResizeObserver detects a change to its size
|
||||
* code extracted from https://github.com/jaredLunde/react-hook/blob/master/packages/resize-observer/src/index.tsx in order to not include the polyfill for resize-observer
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function useResizeObserver<T extends HTMLElement>(
|
||||
target: React.RefObject<T>,
|
||||
callback: UseResizeObserverCallback
|
||||
) {
|
||||
const resizeObserver = getResizeObserver()
|
||||
const storedCallback = useLatest(callback)
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
let didUnsubscribe = false
|
||||
|
||||
const targetEl = target.current
|
||||
if (!targetEl) return
|
||||
|
||||
function cb(entry: ResizeObserverEntry, observer: ResizeObserver) {
|
||||
if (didUnsubscribe) return
|
||||
storedCallback.current(entry, observer)
|
||||
}
|
||||
|
||||
resizeObserver?.subscribe(targetEl as HTMLElement, cb)
|
||||
|
||||
return () => {
|
||||
didUnsubscribe = true
|
||||
resizeObserver?.unsubscribe(targetEl as HTMLElement, cb)
|
||||
}
|
||||
}, [target.current, resizeObserver, storedCallback])
|
||||
|
||||
return resizeObserver?.observer
|
||||
}
|
||||
|
||||
function createResizeObserver() {
|
||||
let ticking = false
|
||||
let allEntries: ResizeObserverEntry[] = []
|
||||
|
||||
const callbacks: Map<unknown, Array<UseResizeObserverCallback>> = new Map()
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(
|
||||
(entries: ResizeObserverEntry[], obs: ResizeObserver) => {
|
||||
allEntries = allEntries.concat(entries)
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
const triggered = new Set<Element>()
|
||||
for (let i = 0; i < allEntries.length; i++) {
|
||||
if (triggered.has(allEntries[i].target)) continue
|
||||
triggered.add(allEntries[i].target)
|
||||
const cbs = callbacks.get(allEntries[i].target)
|
||||
cbs?.forEach((cb) => cb(allEntries[i], obs))
|
||||
}
|
||||
allEntries = []
|
||||
ticking = false
|
||||
})
|
||||
}
|
||||
ticking = true
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
observer,
|
||||
subscribe(target: HTMLElement, callback: UseResizeObserverCallback) {
|
||||
observer.observe(target)
|
||||
const cbs = callbacks.get(target) ?? []
|
||||
cbs.push(callback)
|
||||
callbacks.set(target, cbs)
|
||||
},
|
||||
unsubscribe(target: HTMLElement, callback: UseResizeObserverCallback) {
|
||||
const cbs = callbacks.get(target) ?? []
|
||||
if (cbs.length === 1) {
|
||||
observer.unobserve(target)
|
||||
callbacks.delete(target)
|
||||
return
|
||||
}
|
||||
const cbIndex = cbs.indexOf(callback)
|
||||
if (cbIndex !== -1) cbs.splice(cbIndex, 1)
|
||||
callbacks.set(target, cbs)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let _resizeObserver: ReturnType<typeof createResizeObserver>
|
||||
|
||||
const getResizeObserver = () =>
|
||||
!_resizeObserver
|
||||
? (_resizeObserver = createResizeObserver())
|
||||
: _resizeObserver
|
||||
|
||||
export type UseResizeObserverCallback = (
|
||||
entry: ResizeObserverEntry,
|
||||
observer: ResizeObserver
|
||||
) => unknown
|
||||
|
||||
export const useSize = (target: React.RefObject<HTMLDivElement>) => {
|
||||
const [size, setSize] = React.useState({ width: 0, height: 0 })
|
||||
React.useLayoutEffect(() => {
|
||||
if (target.current) {
|
||||
const { width, height } = target.current.getBoundingClientRect()
|
||||
setSize({ width, height })
|
||||
}
|
||||
}, [target.current])
|
||||
|
||||
const resizeCallback = React.useCallback(
|
||||
(entry: ResizeObserverEntry) => setSize(entry.contentRect),
|
||||
[]
|
||||
)
|
||||
// Where the magic happens
|
||||
useResizeObserver(target, resizeCallback)
|
||||
return size
|
||||
}
|
||||
@@ -4,13 +4,13 @@ import { ParticipantsToggle } from '../../components/controls/Participants/Parti
|
||||
import { ToolsToggle } from '../../components/controls/ToolsToggle'
|
||||
import { InfoToggle } from '../../components/controls/InfoToggle'
|
||||
import { AdminToggle } from '../../components/AdminToggle'
|
||||
import { useSize } from '../../hooks/useResizeObserver'
|
||||
import { useState, RefObject } from 'react'
|
||||
import { Dialog, DialogTrigger, Popover } from 'react-aria-components'
|
||||
import { Button } from '@/primitives'
|
||||
import { ToggleButtonProps } from '@/primitives/ToggleButton'
|
||||
import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSize from '@react-hook/size'
|
||||
|
||||
const CONTROL_BAR_BREAKPOINT = 1100
|
||||
|
||||
@@ -70,7 +70,7 @@ export const MoreOptions = ({
|
||||
}: {
|
||||
parentElement: RefObject<HTMLDivElement>
|
||||
}) => {
|
||||
const parentWidth = useSize(parentElement)[0]
|
||||
const { width: parentWidth } = useSize(parentElement)
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RoomEvent, Track } from 'livekit-client'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
CarouselLayout,
|
||||
ConnectionStateToast,
|
||||
FocusLayoutContainer,
|
||||
GridLayout,
|
||||
@@ -31,7 +32,6 @@ import { RecordingStateToast } from '@/features/recording'
|
||||
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
||||
import { useConnectionObserver } from '../hooks/useConnectionObserver'
|
||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||
import { CarouselLayout } from '../components/layout/CarouselLayout'
|
||||
|
||||
const LayoutWrapper = styled(
|
||||
'div',
|
||||
|
||||
@@ -14,8 +14,8 @@ import { AccountTab } from './tabs/AccountTab'
|
||||
import { NotificationsTab } from './tabs/NotificationsTab'
|
||||
import { GeneralTab } from './tabs/GeneralTab'
|
||||
import { AudioTab } from './tabs/AudioTab'
|
||||
import { useSize } from '@/features/rooms/livekit/hooks/useResizeObserver'
|
||||
import { useRef } from 'react'
|
||||
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
|
||||
|
||||
const tabsStyle = css({
|
||||
maxHeight: '40.625rem', // fixme size copied from meet settings modal
|
||||
@@ -51,7 +51,8 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
||||
const { t } = useTranslation('settings')
|
||||
|
||||
const dialogEl = useRef<HTMLDivElement>(null)
|
||||
const isWideScreen = useMediaQuery('(min-width: 800px)') // fixme - hardcoded 50rem in pixel
|
||||
const { width } = useSize(dialogEl)
|
||||
const isWideScreen = !width || width >= 800 // fixme - hardcoded 50rem in pixel
|
||||
|
||||
return (
|
||||
<Dialog innerRef={dialogEl} {...props} role="dialog" type="flex">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DialogProps, Field, H, Switch, Text } from '@/primitives'
|
||||
import { DialogProps, Field, H, Switch } from '@/primitives'
|
||||
|
||||
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
|
||||
import {
|
||||
@@ -174,7 +174,7 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
</RowWrapper>
|
||||
{/* Safari has a known limitation where its implementation of 'enumerateDevices' does not include audio output devices.
|
||||
To prevent errors or an empty selection list, we only render the speakers selection field on non-Safari browsers. */}
|
||||
{!isSafari() ? (
|
||||
{!isSafari() && (
|
||||
<RowWrapper heading={t('audio.speakers.heading')}>
|
||||
<Field
|
||||
type="select"
|
||||
@@ -193,13 +193,6 @@ export const AudioTab = ({ id }: AudioTabProps) => {
|
||||
/>
|
||||
<SoundTester />
|
||||
</RowWrapper>
|
||||
) : (
|
||||
<RowWrapper heading={t('audio.speakers.heading')}>
|
||||
<Text variant="warning" margin="md">
|
||||
{t('audio.speakers.safariWarning')}
|
||||
</Text>
|
||||
<div />
|
||||
</RowWrapper>
|
||||
)}
|
||||
{noiseReductionAvailable && (
|
||||
<RowWrapper heading={t('audio.noiseReduction.heading')} beta>
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"heading": "Lautsprecher",
|
||||
"label": "Wählen Sie Ihre Audioausgabe",
|
||||
"test": "Testen",
|
||||
"ongoingTest": "Soundtest läuft…",
|
||||
"safariWarning": "Die Lautsprecherauswahl ist auf Safari aufgrund von Browser-Einschränkungen noch nicht verfügbar."
|
||||
"ongoingTest": "Soundtest läuft…"
|
||||
},
|
||||
"permissionsRequired": "Berechtigungen erforderlich"
|
||||
},
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"heading": "Speakers",
|
||||
"label": "Select your audio output",
|
||||
"test": "Test",
|
||||
"ongoingTest": "Testing sound…",
|
||||
"safariWarning": "Speaker selection is not available yet on Safari due to browser limitations."
|
||||
"ongoingTest": "Testing sound…"
|
||||
},
|
||||
"permissionsRequired": "Permissions required"
|
||||
},
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"heading": "Haut-parleurs",
|
||||
"label": "Sélectionner votre sortie audio",
|
||||
"test": "Tester",
|
||||
"ongoingTest": "Test du son…",
|
||||
"safariWarning": "La sélection du haut-parleur n'est pas encore disponible sur Safari en raison de limitations du navigateur."
|
||||
"ongoingTest": "Test du son…"
|
||||
},
|
||||
"permissionsRequired": "Autorisations nécessaires"
|
||||
},
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
"heading": "Luidsprekers",
|
||||
"label": "Selecteer uw audio-uitvoer",
|
||||
"test": "Test",
|
||||
"ongoingTest": "Testgeluid ...",
|
||||
"safariWarning": "Luidsprekerselectie is nog niet beschikbaar op Safari vanwege browserbeperkingen."
|
||||
"ongoingTest": "Testgeluid ..."
|
||||
},
|
||||
"permissionsRequired": "Machtigingen vereist"
|
||||
},
|
||||
|
||||
@@ -53,9 +53,6 @@ export const text = cva({
|
||||
note: {
|
||||
color: 'default.subtle-text',
|
||||
},
|
||||
warning: {
|
||||
color: 'danger.subtle-text',
|
||||
},
|
||||
smNote: {
|
||||
color: 'default.subtle-text',
|
||||
textStyle: 'sm',
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.5.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sdk",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"./library",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "0.1.30",
|
||||
"version": "0.1.29",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "summary"
|
||||
version = "0.1.30"
|
||||
version = "0.1.29"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.105.0",
|
||||
"uvicorn>=0.24.0",
|
||||
|
||||
@@ -107,13 +107,12 @@ class MetadataManager:
|
||||
"retries": 0,
|
||||
}
|
||||
|
||||
_required_args_count = 7
|
||||
_required_args_count = 4
|
||||
if len(task_args) != _required_args_count:
|
||||
logger.error("Invalid number of arguments.")
|
||||
return
|
||||
|
||||
filename, email, _, received_at, *_ = task_args
|
||||
|
||||
filename, email, _, received_at = task_args
|
||||
initial_metadata = {
|
||||
**initial_metadata,
|
||||
"filename": filename,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""Celery Config."""
|
||||
|
||||
# https://github.com/danihodovic/celery-exporter
|
||||
# Enable task events for Prometheus monitoring via celery-exporter.
|
||||
|
||||
# worker_send_task_events: Sends task lifecycle events (e.g., started, succeeded),
|
||||
# worker_send_task_events: Sends task lifecycle events (e.g., started, succeeded, failed),
|
||||
# allowing the exporter to track task execution metrics and durations.
|
||||
worker_send_task_events = True
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ celery = Celery(
|
||||
broker_connection_retry_on_startup=True,
|
||||
)
|
||||
|
||||
celery.config_from_object("summary.core.celery_config")
|
||||
celery.config_from_object('summary.core.celery_config')
|
||||
|
||||
if settings.sentry_dsn and settings.sentry_is_enabled:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user