Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80fdf3faf2 | |||
| 8d2df4001a | |||
| d610c0dff3 | |||
| bad054fa8c | |||
| 3b398ca3fd | |||
| 3c2c1a04db | |||
| cb30048b27 | |||
| 945279d504 | |||
| 13c9436dc3 |
@@ -5,7 +5,6 @@ import { Screen } from '@/layout/Screen'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { LocalVideoTrack, Track } from 'livekit-client'
|
||||
import { H } from '@/primitives/H'
|
||||
import { SelectToggleDevice } from '../livekit/components/controls/SelectToggleDevice'
|
||||
import { Field } from '@/primitives/Field'
|
||||
import { Button, Dialog, Text, Form } from '@/primitives'
|
||||
import { VStack } from '@/styled-system/jsx'
|
||||
@@ -29,6 +28,9 @@ import { ApiAccessLevel } from '../api/ApiRoom'
|
||||
import { useLoginHint } from '@/hooks/useLoginHint'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { openPermissionsDialog, permissionsStore } from '@/stores/permissions'
|
||||
import { ToggleDevice } from './join/ToggleDevice'
|
||||
import { SelectDevice } from './join/SelectDevice'
|
||||
import { useResolveDefaultDeviceId } from '../livekit/hooks/useResolveDefaultDevice'
|
||||
|
||||
const onError = (e: Error) => console.error('ERROR', e)
|
||||
|
||||
@@ -139,6 +141,11 @@ export const Join = ({
|
||||
[tracks]
|
||||
)
|
||||
|
||||
// LiveKit by default populates device choices with "default" value.
|
||||
// Instead, use the current device id used by the preview track as a default
|
||||
useResolveDefaultDeviceId(audioDeviceId, audioTrack, saveAudioInputDeviceId)
|
||||
useResolveDefaultDeviceId(videoDeviceId, videoTrack, saveVideoInputDeviceId)
|
||||
|
||||
const videoEl = useRef(null)
|
||||
const isVideoInitiated = useRef(false)
|
||||
|
||||
@@ -160,7 +167,11 @@ export const Join = ({
|
||||
|
||||
return () => {
|
||||
videoTrack?.detach()
|
||||
videoElement?.removeEventListener('loadedmetadata', handleVideoLoaded)
|
||||
if (videoElement) {
|
||||
videoElement.removeEventListener('loadedmetadata', handleVideoLoaded)
|
||||
videoElement.style.opacity = '0'
|
||||
}
|
||||
isVideoInitiated.current = false
|
||||
}
|
||||
}, [videoTrack, videoEnabled])
|
||||
|
||||
@@ -399,6 +410,28 @@ export const Join = ({
|
||||
alignItems: 'center',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: '5rem',
|
||||
width: '100%',
|
||||
backgroundImage:
|
||||
'linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.1) 80%, rgba(0, 0, 0, 0) 100%)',
|
||||
zIndex: 1,
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
height: '5rem',
|
||||
width: '100%',
|
||||
backgroundImage:
|
||||
'linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.3) 35%, rgba(0, 0, 0, 0.1) 75%, rgba(0, 0, 0, 0) 100%)',
|
||||
zIndex: 1,
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
className={css({
|
||||
position: 'relative',
|
||||
@@ -502,6 +535,33 @@ export const Join = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
bottom: '1rem',
|
||||
zIndex: '1',
|
||||
display: 'flex',
|
||||
gap: '1rem',
|
||||
justifyContent: 'center',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
})}
|
||||
>
|
||||
<ToggleDevice
|
||||
source={Track.Source.Microphone}
|
||||
initialState={audioEnabled}
|
||||
track={audioTrack}
|
||||
onChange={(enabled) => saveAudioInputEnabled(enabled)}
|
||||
onDeviceError={(error) => console.error(error)}
|
||||
/>
|
||||
<ToggleDevice
|
||||
source={Track.Source.Camera}
|
||||
initialState={videoEnabled}
|
||||
track={videoTrack}
|
||||
onChange={(enabled) => saveVideoInputEnabled(enabled)}
|
||||
onDeviceError={(error) => console.error(error)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
@@ -528,32 +588,26 @@ export const Join = ({
|
||||
marginX: 'auto',
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Microphone}
|
||||
initialState={audioEnabled}
|
||||
track={audioTrack}
|
||||
initialDeviceId={audioDeviceId}
|
||||
onChange={(enabled) => saveAudioInputEnabled(enabled)}
|
||||
onDeviceError={(error) => console.error(error)}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveAudioInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
variant="tertiary"
|
||||
<div
|
||||
className={css({
|
||||
width: '50%',
|
||||
})}
|
||||
>
|
||||
<SelectDevice
|
||||
kind="audioinput"
|
||||
id={audioDeviceId}
|
||||
onSubmit={saveAudioInputDeviceId}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<SelectToggleDevice
|
||||
source={Track.Source.Camera}
|
||||
initialState={videoEnabled}
|
||||
track={videoTrack}
|
||||
initialDeviceId={videoDeviceId}
|
||||
onChange={(enabled) => saveVideoInputEnabled(enabled)}
|
||||
onDeviceError={(error) => console.error(error)}
|
||||
onActiveDeviceChange={(deviceId) =>
|
||||
saveVideoInputDeviceId(deviceId ?? '')
|
||||
}
|
||||
variant="tertiary"
|
||||
<div
|
||||
className={css({
|
||||
width: '50%',
|
||||
})}
|
||||
>
|
||||
<SelectDevice
|
||||
kind="videoinput"
|
||||
id={videoDeviceId}
|
||||
onSubmit={saveVideoInputDeviceId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
RemixiconComponentType,
|
||||
RiMicLine,
|
||||
RiVideoOnLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMediaDeviceSelect } from '@livekit/components-react'
|
||||
import { useMemo } from 'react'
|
||||
import { Select } from '@/primitives/Select'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { permissionsStore } from '@/stores/permissions'
|
||||
|
||||
type DeviceItems = Array<{ value: string; label: string }>
|
||||
|
||||
type DeviceConfig = {
|
||||
icon: RemixiconComponentType
|
||||
}
|
||||
|
||||
type SelectDeviceProps = {
|
||||
id?: string
|
||||
onSubmit?: (id: string) => void
|
||||
kind: MediaDeviceKind
|
||||
}
|
||||
|
||||
type SelectDevicePermissionsProps = SelectDeviceProps & {
|
||||
config: DeviceConfig
|
||||
}
|
||||
|
||||
const SelectDevicePermissions = ({
|
||||
id,
|
||||
kind,
|
||||
config,
|
||||
onSubmit,
|
||||
}: SelectDevicePermissionsProps) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
|
||||
const { devices, activeDeviceId, setActiveMediaDevice } =
|
||||
useMediaDeviceSelect({ kind: kind, requestPermissions: true })
|
||||
|
||||
const items: DeviceItems = devices
|
||||
.filter((d) => !!d.deviceId)
|
||||
.map((d) => ({
|
||||
value: d.deviceId,
|
||||
label: d.label,
|
||||
}))
|
||||
|
||||
return (
|
||||
<Select
|
||||
aria-label={t(`${kind}.choose`)}
|
||||
label=""
|
||||
isDisabled={items.length === 0}
|
||||
items={items}
|
||||
iconComponent={config?.icon}
|
||||
placeholder={t('selectDevice.loading')}
|
||||
selectedKey={id || activeDeviceId}
|
||||
onSelectionChange={(key) => {
|
||||
onSubmit?.(key as string)
|
||||
setActiveMediaDevice(key as string)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const SelectDevice = ({ id, onSubmit, kind }: SelectDeviceProps) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
|
||||
const permissions = useSnapshot(permissionsStore)
|
||||
|
||||
const config = useMemo<DeviceConfig | undefined>(() => {
|
||||
switch (kind) {
|
||||
case 'audioinput':
|
||||
return {
|
||||
icon: RiMicLine,
|
||||
}
|
||||
case 'videoinput':
|
||||
return {
|
||||
icon: RiVideoOnLine,
|
||||
}
|
||||
}
|
||||
}, [kind])
|
||||
|
||||
const isPermissionDeniedOrPrompted = useMemo(() => {
|
||||
if (kind == 'audioinput') {
|
||||
return permissions.isMicrophoneDenied || permissions.isMicrophonePrompted
|
||||
}
|
||||
if (kind == 'videoinput') {
|
||||
return permissions.isCameraDenied || permissions.isCameraPrompted
|
||||
}
|
||||
return false
|
||||
}, [kind, permissions])
|
||||
|
||||
if (!config) return null
|
||||
|
||||
if (isPermissionDeniedOrPrompted || permissions.isLoading) {
|
||||
return (
|
||||
<Select
|
||||
aria-label={t(`${kind}.permissionsNeeded`)}
|
||||
label=""
|
||||
isDisabled={true}
|
||||
items={[]}
|
||||
iconComponent={config?.icon}
|
||||
placeholder={t('selectDevice.permissionsNeeded')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectDevicePermissions
|
||||
id={id}
|
||||
onSubmit={onSubmit}
|
||||
kind={kind}
|
||||
config={config}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { UseTrackToggleProps } from '@livekit/components-react'
|
||||
import { ToggleDevice as BaseToggleDevice } from '../../livekit/components/controls/ToggleDevice'
|
||||
import {
|
||||
TOGGLE_DEVICE_CONFIG,
|
||||
ToggleSource,
|
||||
} from '../../livekit/config/ToggleDeviceConfig'
|
||||
import { LocalAudioTrack, LocalVideoTrack } from 'livekit-client'
|
||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { permissionsStore } from '@/stores/permissions'
|
||||
|
||||
type ToggleDeviceProps<T extends ToggleSource> = UseTrackToggleProps<T> & {
|
||||
track?: LocalAudioTrack | LocalVideoTrack
|
||||
source: ToggleSource
|
||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
}
|
||||
|
||||
export const ToggleDevice = <T extends ToggleSource>({
|
||||
track,
|
||||
onChange,
|
||||
...props
|
||||
}: ToggleDeviceProps<T>) => {
|
||||
const config = TOGGLE_DEVICE_CONFIG[props.source]
|
||||
|
||||
if (!config) {
|
||||
throw new Error('Invalid source')
|
||||
}
|
||||
|
||||
const [isTrackEnabled, setIsTrackEnabled] = useState(
|
||||
props.initialState ?? false
|
||||
)
|
||||
|
||||
const permissions = useSnapshot(permissionsStore)
|
||||
|
||||
const isPermissionDeniedOrPrompted = useMemo(() => {
|
||||
if (config.kind == 'audioinput') {
|
||||
return permissions.isMicrophoneDenied || permissions.isMicrophonePrompted
|
||||
}
|
||||
if (config.kind == 'videoinput') {
|
||||
return permissions.isCameraDenied || permissions.isCameraPrompted
|
||||
}
|
||||
}, [config, permissions])
|
||||
|
||||
const toggle = useCallback(async () => {
|
||||
if (!track) {
|
||||
console.error('Track is undefined.')
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (isTrackEnabled) {
|
||||
await track.mute()
|
||||
setIsTrackEnabled(false)
|
||||
onChange?.(false, true)
|
||||
} else {
|
||||
await track.unmute()
|
||||
setIsTrackEnabled(true)
|
||||
onChange?.(true, true)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle track:', error)
|
||||
}
|
||||
}, [track, onChange, isTrackEnabled])
|
||||
|
||||
return (
|
||||
<BaseToggleDevice
|
||||
enabled={isTrackEnabled}
|
||||
isPermissionDeniedOrPrompted={isPermissionDeniedOrPrompted}
|
||||
toggle={toggle}
|
||||
config={config}
|
||||
variant="whiteCircle"
|
||||
errorVariant="errorCircle"
|
||||
toggleButtonProps={{
|
||||
groupPosition: undefined,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -5,14 +5,7 @@ import {
|
||||
UseTrackToggleProps,
|
||||
} from '@livekit/components-react'
|
||||
import { Button, Menu, MenuList } from '@/primitives'
|
||||
import {
|
||||
RemixiconComponentType,
|
||||
RiArrowDownSLine,
|
||||
RiMicLine,
|
||||
RiMicOffLine,
|
||||
RiVideoOffLine,
|
||||
RiVideoOnLine,
|
||||
} from '@remixicon/react'
|
||||
import { RiArrowUpSLine } from '@remixicon/react'
|
||||
import {
|
||||
LocalAudioTrack,
|
||||
LocalVideoTrack,
|
||||
@@ -20,8 +13,6 @@ import {
|
||||
VideoCaptureOptions,
|
||||
} from 'livekit-client'
|
||||
|
||||
import { Shortcut } from '@/features/shortcuts/types'
|
||||
|
||||
import { ToggleDevice } from '@/features/rooms/livekit/components/controls/ToggleDevice.tsx'
|
||||
import { css } from '@/styled-system/css'
|
||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
||||
@@ -30,56 +21,17 @@ import { usePersistentUserChoices } from '../../hooks/usePersistentUserChoices'
|
||||
import { BackgroundProcessorFactory } from '../blur'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { permissionsStore } from '@/stores/permissions'
|
||||
|
||||
export type ToggleSource = Exclude<
|
||||
Track.Source,
|
||||
Track.Source.ScreenShareAudio | Track.Source.Unknown
|
||||
>
|
||||
|
||||
type SelectToggleSource = Exclude<ToggleSource, Track.Source.ScreenShare>
|
||||
|
||||
export type SelectToggleDeviceConfig = {
|
||||
kind: MediaDeviceKind
|
||||
iconOn: RemixiconComponentType
|
||||
iconOff: RemixiconComponentType
|
||||
shortcut?: Shortcut
|
||||
longPress?: Shortcut
|
||||
}
|
||||
|
||||
type SelectToggleDeviceConfigMap = {
|
||||
[key in SelectToggleSource]: SelectToggleDeviceConfig
|
||||
}
|
||||
|
||||
const selectToggleDeviceConfig: SelectToggleDeviceConfigMap = {
|
||||
[Track.Source.Microphone]: {
|
||||
kind: 'audioinput',
|
||||
iconOn: RiMicLine,
|
||||
iconOff: RiMicOffLine,
|
||||
shortcut: {
|
||||
key: 'd',
|
||||
ctrlKey: true,
|
||||
},
|
||||
longPress: {
|
||||
key: 'Space',
|
||||
},
|
||||
},
|
||||
[Track.Source.Camera]: {
|
||||
kind: 'videoinput',
|
||||
iconOn: RiVideoOnLine,
|
||||
iconOff: RiVideoOffLine,
|
||||
shortcut: {
|
||||
key: 'e',
|
||||
ctrlKey: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
import {
|
||||
TOGGLE_DEVICE_CONFIG,
|
||||
ToggleSource,
|
||||
} from '../../config/ToggleDeviceConfig'
|
||||
|
||||
type SelectToggleDeviceProps<T extends ToggleSource> =
|
||||
UseTrackToggleProps<T> & {
|
||||
track?: LocalAudioTrack | LocalVideoTrack | undefined
|
||||
track?: LocalAudioTrack | LocalVideoTrack
|
||||
initialDeviceId?: string
|
||||
onActiveDeviceChange: (deviceId: string) => void
|
||||
source: SelectToggleSource
|
||||
source: ToggleSource
|
||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
menuVariant?: 'dark' | 'light'
|
||||
hideMenu?: boolean
|
||||
@@ -94,7 +46,7 @@ export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
menuVariant = 'light',
|
||||
...props
|
||||
}: SelectToggleDeviceProps<T>) => {
|
||||
const config = selectToggleDeviceConfig[props.source]
|
||||
const config = TOGGLE_DEVICE_CONFIG[props.source]
|
||||
if (!config) {
|
||||
throw new Error('Invalid source')
|
||||
}
|
||||
@@ -198,7 +150,7 @@ export const SelectToggleDevice = <T extends ToggleSource>({
|
||||
: 'error2'
|
||||
}
|
||||
>
|
||||
<RiArrowDownSLine />
|
||||
<RiArrowUpSLine />
|
||||
</Button>
|
||||
<MenuList
|
||||
items={devices.map((d) => ({
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKey
|
||||
import { useMemo, useState } from 'react'
|
||||
import { appendShortcutLabel } from '@/features/shortcuts/utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SelectToggleDeviceConfig } from './SelectToggleDevice'
|
||||
import { PermissionNeededButton } from './PermissionNeededButton'
|
||||
import useLongPress from '@/features/shortcuts/useLongPress'
|
||||
import { ActiveSpeaker } from '@/features/rooms/components/ActiveSpeaker'
|
||||
@@ -15,13 +14,15 @@ import {
|
||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
||||
import { ToggleButtonProps } from '@/primitives/ToggleButton'
|
||||
import { openPermissionsDialog } from '@/stores/permissions'
|
||||
import { ToggleDeviceConfig } from '../../config/ToggleDeviceConfig'
|
||||
|
||||
export type ToggleDeviceProps = {
|
||||
enabled: boolean
|
||||
isPermissionDeniedOrPrompted?: boolean
|
||||
toggle: () => void
|
||||
config: SelectToggleDeviceConfig
|
||||
config: ToggleDeviceConfig
|
||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
errorVariant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
toggleButtonProps?: Partial<ToggleButtonProps>
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ export const ToggleDevice = ({
|
||||
enabled,
|
||||
toggle,
|
||||
variant = 'primaryDark',
|
||||
errorVariant = 'error2',
|
||||
toggleButtonProps,
|
||||
isPermissionDeniedOrPrompted,
|
||||
}: ToggleDeviceProps) => {
|
||||
@@ -72,7 +74,9 @@ export const ToggleDevice = ({
|
||||
{isPermissionDeniedOrPrompted && <PermissionNeededButton />}
|
||||
<ToggleButton
|
||||
isSelected={!enabled}
|
||||
variant={enabled && !isPermissionDeniedOrPrompted ? variant : 'error2'}
|
||||
variant={
|
||||
enabled && !isPermissionDeniedOrPrompted ? variant : errorVariant
|
||||
}
|
||||
shySelected
|
||||
onPress={() =>
|
||||
isPermissionDeniedOrPrompted ? openPermissionsDialog() : toggle()
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Track } from 'livekit-client'
|
||||
import {
|
||||
RemixiconComponentType,
|
||||
RiMicLine,
|
||||
RiMicOffLine,
|
||||
RiVideoOffLine,
|
||||
RiVideoOnLine,
|
||||
} from '@remixicon/react'
|
||||
import { Shortcut } from '@/features/shortcuts/types'
|
||||
|
||||
export type ToggleSource = Exclude<
|
||||
Track.Source,
|
||||
| Track.Source.ScreenShareAudio
|
||||
| Track.Source.Unknown
|
||||
| Track.Source.ScreenShare
|
||||
>
|
||||
|
||||
export type ToggleDeviceConfig = {
|
||||
kind: MediaDeviceKind
|
||||
iconOn: RemixiconComponentType
|
||||
iconOff: RemixiconComponentType
|
||||
shortcut?: Shortcut
|
||||
longPress?: Shortcut
|
||||
}
|
||||
|
||||
type ToggleDeviceConfigMap = {
|
||||
[key in ToggleSource]: ToggleDeviceConfig
|
||||
}
|
||||
|
||||
export const TOGGLE_DEVICE_CONFIG = {
|
||||
[Track.Source.Microphone]: {
|
||||
kind: 'audioinput',
|
||||
iconOn: RiMicLine,
|
||||
iconOff: RiMicOffLine,
|
||||
shortcut: {
|
||||
key: 'd',
|
||||
ctrlKey: true,
|
||||
},
|
||||
longPress: {
|
||||
key: 'Space',
|
||||
},
|
||||
},
|
||||
[Track.Source.Camera]: {
|
||||
kind: 'videoinput',
|
||||
iconOn: RiVideoOnLine,
|
||||
iconOff: RiVideoOffLine,
|
||||
shortcut: {
|
||||
key: 'e',
|
||||
ctrlKey: true,
|
||||
},
|
||||
},
|
||||
} as const satisfies ToggleDeviceConfigMap
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export const useResolveDefaultDeviceId = <
|
||||
T extends { getDeviceId(): Promise<string | undefined> },
|
||||
>(
|
||||
currentId: string,
|
||||
track: T | undefined,
|
||||
save: (id: string) => void
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (currentId !== 'default' || !track) return
|
||||
const resolveDefaultDeviceId = async () => {
|
||||
const actualDeviceId = await track.getDeviceId()
|
||||
if (actualDeviceId && actualDeviceId !== 'default') save(actualDeviceId)
|
||||
}
|
||||
resolveDefaultDeviceId()
|
||||
}, [currentId, track, save])
|
||||
}
|
||||
@@ -8,8 +8,13 @@
|
||||
"back": "Dem Meeting erneut beitreten"
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Laden…",
|
||||
"permissionsNeeded": "Genehmigung erforderlich"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Kamera auswählen",
|
||||
"permissionsNeeded": "Kamera auswählen - genehmigung erforderlich",
|
||||
"disable": "Kamera deaktivieren",
|
||||
"enable": "Kamera aktivieren",
|
||||
"label": "Kamera",
|
||||
@@ -17,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Mikrofon auswählen",
|
||||
"permissionsNeeded": "Mikrofon auswählen - genehmigung erforderlich",
|
||||
"disable": "Mikrofon deaktivieren",
|
||||
"enable": "Mikrofon aktivieren",
|
||||
"label": "Mikrofon"
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
"back": "Rejoin the meeting"
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Loading…",
|
||||
"permissionsNeeded": "Permission needed"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Select camera",
|
||||
"permissionsNeeded": "Select camera - permission needed",
|
||||
"disable": "Disable camera",
|
||||
"enable": "Enable camera",
|
||||
"label": "Camera",
|
||||
@@ -17,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Select microphone",
|
||||
"permissionsNeeded": "Select microphone - permission needed",
|
||||
"disable": "Disable microphone",
|
||||
"enable": "Enable microphone",
|
||||
"label": "Microphone"
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
"back": "Réintégrer la réunion"
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Chargement…",
|
||||
"permissionsNeeded": "Autorisations nécessaires"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Choisir la webcam",
|
||||
"permissionsNeeded": "Choisir la webcam - autorisations nécessaires",
|
||||
"disable": "Désactiver la webcam",
|
||||
"enable": "Activer la webcam",
|
||||
"label": "Webcam",
|
||||
@@ -17,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Choisir le micro",
|
||||
"permissionsNeeded": "Choisir le micro - autorisations nécessaires",
|
||||
"disable": "Désactiver le micro",
|
||||
"enable": "Activer le micro",
|
||||
"label": "Microphone"
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
"back": "Sluit weer bij de vergadering aan"
|
||||
},
|
||||
"join": {
|
||||
"selectDevice": {
|
||||
"loading": "Bezig met laden…",
|
||||
"permissionNeeded": "Toestemming vereist"
|
||||
},
|
||||
"videoinput": {
|
||||
"choose": "Selecteer camera",
|
||||
"permissionNeeded": "Selecteer camera - Toestemming vereist",
|
||||
"disable": "Camera uitschakelen",
|
||||
"enable": "Camera inschakelen",
|
||||
"label": "Camera",
|
||||
@@ -17,6 +22,7 @@
|
||||
},
|
||||
"audioinput": {
|
||||
"choose": "Selecteer microfoon",
|
||||
"permissionNeeded": "Selecteer microfoon - Toestemming vereist",
|
||||
"disable": "Microfoon dempen",
|
||||
"enable": "Microfoon dempen opheffen",
|
||||
"label": "Microfoon"
|
||||
|
||||
@@ -251,6 +251,20 @@ export const buttonRecipe = cva({
|
||||
color: 'error.300',
|
||||
},
|
||||
},
|
||||
errorCircle: {
|
||||
backgroundColor: 'error.500',
|
||||
width: '56px',
|
||||
height: '56px',
|
||||
borderRadius: '100%',
|
||||
color: 'white',
|
||||
'&[data-hovered]': {
|
||||
backgroundColor: 'error.600',
|
||||
},
|
||||
'&[data-pressed]': {
|
||||
backgroundColor: 'error.700',
|
||||
color: 'error.200',
|
||||
},
|
||||
},
|
||||
// @TODO: better handling of colors… this is a mess
|
||||
success: {
|
||||
colorPalette: 'success',
|
||||
|
||||
Reference in New Issue
Block a user