Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ and this project adheres to
- ♿️(frontend) improve accessibility of the IntroSlider carousel #1026
- ♿️(frontend) add skip link component for keyboard navigation #1019
- ♿️(frontend) announce mic/camera state to SR on shortcut toggle #1052
- ✨(frontend) add Ctrl+Shift+/ to open shortcuts settings #1050

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,9 @@ export const ParticipantTile: (
)}
</ParticipantContextIfNeeded>
</TrackRefContextIfNeeded>
{hasKeyboardFocus && (
<div className="shortcut-hint-wrapper">
<KeyboardShortcutHint>{t('toolbarHint')}</KeyboardShortcutHint>
)}
</div>
</div>
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { useSidePanel } from '../../hooks/useSidePanel'
import { css } from '@/styled-system/css'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'

export const ToolsToggle = ({
variant = 'primaryTextDark',
Expand All @@ -15,6 +16,11 @@ export const ToolsToggle = ({
const { isToolsOpen, toggleTools } = useSidePanel()
const tooltipLabel = isToolsOpen ? 'open' : 'closed'

useRegisterKeyboardShortcut({
id: 'recording',
handler: toggleTools,
})

return (
<div
className={css({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { StartMediaButton } from '../../components/controls/StartMediaButton'
import { MoreOptions } from './MoreOptions'
import { useRef } from 'react'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { useFullScreen } from '../../hooks/useFullScreen'
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'

Expand All @@ -21,6 +22,8 @@ export function DesktopControlBar({
const browserSupportsScreenSharing = supportsScreenSharing()
const desktopControlBarEl = useRef<HTMLDivElement>(null)

const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({})

useRegisterKeyboardShortcut({
id: 'focus-toolbar',
handler: () => {
Expand All @@ -32,6 +35,13 @@ export function DesktopControlBar({
firstButton?.focus()
},
})

useRegisterKeyboardShortcut({
id: 'fullscreen',
handler: toggleFullScreen,
isDisabled: !isFullscreenAvailable,
})

return (
<div
ref={desktopControlBarEl}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import { RecordingProvider } from '@/features/recording'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
import { useConnectionObserver } from '../hooks/useConnectionObserver'
import { useNoiseReduction } from '../hooks/useNoiseReduction'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import { useSettingsDialog } from '@/features/settings'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription'
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
Expand Down Expand Up @@ -97,6 +100,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
const { t: tRooms } = useTranslation('rooms')
const room = useRoomContext()
const announce = useScreenReaderAnnounce()
const { toggleSettingsDialog } = useSettingsDialog()

const getAnnouncementName = useCallback(
(participant?: Participant | null) => {
Expand All @@ -111,6 +115,13 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
useConnectionObserver()
useVideoResolutionSubscription()

useRegisterKeyboardShortcut({
id: 'open-shortcuts',
handler: useCallback(() => {
toggleSettingsDialog(SettingsDialogExtendedKey.SHORTCUTS)
}, [toggleSettingsDialog]),
})

const tracks = useTracks(
[
{ source: Track.Source.Camera, withPlaceholder: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import { ShortcutRow } from '@/features/shortcuts/components/ShortcutRow'
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { TabPanel, type TabPanelProps } from '@/primitives/Tabs'
import { H } from '@/primitives'

const tableStyle = css({
width: '100%',
borderCollapse: 'collapse',
overflowY: 'auto',
'& caption': {
fontWeight: 'bold',
marginBottom: '0.75rem',
textAlign: 'left',
},
'& th, & td': {
padding: '0.65rem 0',
textAlign: 'left',
fontWeight: 'normal',
},
'& tbody tr': {
borderBottom: '1px solid rgba(255,255,255,0.08)',
Expand All @@ -29,12 +34,11 @@ export const ShortcutTab = ({ id }: Pick<TabPanelProps, 'id'>) => {
className={css({
display: 'flex',
flexDirection: 'column',
gap: '0.75rem',
})}
>
<H lvl={2}>{t('shortcuts.listLabel')}</H>
<table className={tableStyle}>
<thead className="sr-only">
<caption>{t('shortcuts.listLabel')}</caption>
<thead>
<tr>
<th scope="col">{t('shortcuts.columnAction')}</th>
<th scope="col">{t('shortcuts.columnShortcut')}</th>
Expand Down
18 changes: 18 additions & 0 deletions src/frontend/src/features/settings/hook/useSettingsDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,25 @@ export const useSettingsDialog = () => {
settingsStore.areSettingsOpen = true
}

const closeSettingsDialog = () => {
settingsStore.areSettingsOpen = false
}

const toggleSettingsDialog = (
defaultSelectedTab?: SettingsDialogExtendedKey
) => {
if (areSettingsOpen) {
closeSettingsDialog()
} else {
if (defaultSelectedTab)
settingsStore.defaultSelectedTab = defaultSelectedTab
settingsStore.areSettingsOpen = true
}
}

return {
openSettingsDialog,
closeSettingsDialog,
toggleSettingsDialog,
}
}
6 changes: 6 additions & 0 deletions src/frontend/src/features/shortcuts/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Shortcut } from './types'
export type ShortcutCategory = 'navigation' | 'media' | 'interaction'

export type ShortcutId =
| 'open-shortcuts'
| 'focus-toolbar'
| 'toggle-microphone'
| 'toggle-camera'
Expand All @@ -29,6 +30,11 @@ export type ShortcutDescriptor = {
}

export const shortcutCatalog: ShortcutDescriptor[] = [
{
id: 'open-shortcuts',
category: 'navigation',
shortcut: { key: '/', ctrlKey: true, shiftKey: true },
},
{
id: 'focus-toolbar',
category: 'navigation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export const ShortcutBadge: React.FC<ShortcutBadgeProps> = ({
}) => {
return (
<>
<div className={cx(badgeStyle, className)} aria-hidden="true">
<span>{visualLabel}</span>
</div>
<kbd className={cx(badgeStyle, className)} aria-hidden="true">
{visualLabel}
</kbd>
Comment on lines +28 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick : I think the KBD styling should apply only to the actual key being pressed, not to the plus sign. Showing the "+" inside the KBD component could be misleading, as it might suggest that users need to press the "+" key specifically, rather than indicating a key combination.

{srLabel && <span className="sr-only">{srLabel}</span>}
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export const ShortcutRow: React.FC<ShortcutRowProps> = ({ descriptor }) => {

return (
<tr>
<td className={text({ variant: 'body' })}>
<th scope="row" className={text({ variant: 'body' })}>
{t(`actions.${descriptor.id}`)}
</td>
</th>
<td className={shortcutCellStyle}>
<ShortcutBadge visualLabel={visualShortcut} srLabel={srShortcut} />
</td>
Expand Down
5 changes: 4 additions & 1 deletion src/frontend/src/features/shortcuts/useKeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export const useKeyboardShortcuts = () => {
shiftKey,
altKey,
})
const shortcut = shortcutsSnap.shortcuts.get(shortcutKey)
let shortcut = shortcutsSnap.shortcuts.get(shortcutKey)
if (!shortcut && shortcutKey === 'ctrl+shift+?') {
shortcut = shortcutsSnap.shortcuts.get('ctrl+shift+/')
}
if (!shortcut) return
e.preventDefault()
await shortcut()
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/locales/de/rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
},
"participantTileFocus": {
"containerLabel": "Optionen für {{name}}",
"toolbarHint": "F2: zur Symbolleiste unten.",
"toolbarHint": "Ctrl+Shift+/: Direkt auf die Tastenkürzel zugreifen.",
"pin": {
"enable": "Anheften",
"disable": "Lösen"
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/locales/en/rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
},
"participantTileFocus": {
"containerLabel": "Options for {{name}}",
"toolbarHint": "F2: go to the bottom toolbar.",
"toolbarHint": "Ctrl+Shift+/: access shortcuts directly.",
"pin": {
"enable": "Pin",
"disable": "Unpin"
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/locales/fr/rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
},
"participantTileFocus": {
"containerLabel": "Options pour {{name}}",
"toolbarHint": "F2 : raccourci barre d'outils en bas.",
"toolbarHint": "Ctrl+Shift+/ : accéder directement aux raccourcis.",
"pin": {
"enable": "Épingler",
"disable": "Annuler l'épinglage"
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/locales/nl/rooms.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@
},
"participantTileFocus": {
"containerLabel": "Opties voor {{name}}",
"toolbarHint": "F2: naar de werkbalk onderaan.",
"toolbarHint": "Ctrl+Shift+/: direct toegang tot de sneltoetsen.",
"pin": {
"enable": "Pinnen",
"disable": "Losmaken"
Expand Down
13 changes: 13 additions & 0 deletions src/frontend/src/styles/livekit.css
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,16 @@
display: flex;
align-items: center;
}

/* Shortcut hint: visible only on first grid tile when focused (CSS-based) */
.shortcut-hint-wrapper {
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 150ms ease;
}
Comment on lines +176 to +182
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D’après ce que j’ai pu constater, le hint n’apparaît que lorsque tu cliques réellement sur la participant tile, ou sur le menu de métadonnées qui s’affiche au survol.

Autrement dit, le raccourci n’apparaît pas en même temps que le menu d’actions de la participant tile (qui, lui, s’affiche au survol), mais uniquement lorsqu’il y a un véritable focus sur la participant tile.

En y réfléchissant, je me demande si cette fonctionnalité n’est pas surtout pertinente pour les utilisateurs de lecteurs d’écran. Peut-être qu’un jour, on préférera réserver le coin droit à autre chose : on pourra en reparler le moment venu.

En tout cas, ce comportement est différent de ce que j’imaginais : les actions supplémentaires d’une participant tile apparaissent au survol, tandis que le raccourci, lui, n’apparaît qu’au moment où la partie mentale reçoit effectivement le focus.

On peut ship comme ça, c'est un peu nit picking.

.lk-grid-layout > *:first-child:focus-within .shortcut-hint-wrapper {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
Loading