@livekit/components-core
Advanced tools
Comparing version 0.11.0 to 0.11.1
@@ -170,3 +170,32 @@ import type { AudioCaptureOptions } from 'livekit-client'; | ||
/** | ||
* @public | ||
*/ | ||
export declare type GridLayoutDefinition = { | ||
/** Column count of the grid layout. */ | ||
columns: number; | ||
/** Row count of the grid layout. */ | ||
rows: number; | ||
/** | ||
* Minimum grid container width required to use this layout. | ||
* @remarks | ||
* If this constraint is not met, we try to select a layout with fewer tiles | ||
* (`tiles=columns*rows`) that is within the constraint. | ||
*/ | ||
minWidth?: number; | ||
/** | ||
* Minimum grid container height required to use this layout. | ||
* @remarks | ||
* If this constraint is not met, we try to select a layout with fewer tiles | ||
* (`tiles=columns*rows`) that is within the constraint. | ||
*/ | ||
minHeight?: number; | ||
/** | ||
* For which orientation the layout definition should be applied. | ||
* Will be used for both landscape and portrait if no value is specified. | ||
*/ | ||
orientation?: 'landscape' | 'portrait'; | ||
}; | ||
export declare type GridLayoutInfo = { | ||
/** Layout name (convention `<column_count>x<row_count>`). */ | ||
@@ -178,4 +207,2 @@ name: string; | ||
rows: number; | ||
/** Minimum number of tiles needed to use this layout. */ | ||
minTiles: number; | ||
/** Maximum tiles that fit into this layout. */ | ||
@@ -187,2 +214,3 @@ maxTiles: number; | ||
minHeight: number; | ||
orientation?: 'landscape' | 'portrait'; | ||
}; | ||
@@ -443,3 +471,3 @@ | ||
export declare function selectGridLayout(layouts: GridLayoutDefinition[], participantCount: number, width: number, height: number): GridLayoutDefinition; | ||
export declare function selectGridLayout(layoutDefinitions: GridLayoutDefinition[], participantCount: number, width: number, height: number): GridLayoutInfo; | ||
@@ -446,0 +474,0 @@ /** Publish data from the LocalParticipant. */ |
@@ -0,2 +1,30 @@ | ||
/** | ||
* @public | ||
*/ | ||
export type GridLayoutDefinition = { | ||
/** Column count of the grid layout. */ | ||
columns: number; | ||
/** Row count of the grid layout. */ | ||
rows: number; | ||
/** | ||
* Minimum grid container width required to use this layout. | ||
* @remarks | ||
* If this constraint is not met, we try to select a layout with fewer tiles | ||
* (`tiles=columns*rows`) that is within the constraint. | ||
*/ | ||
minWidth?: number; | ||
/** | ||
* Minimum grid container height required to use this layout. | ||
* @remarks | ||
* If this constraint is not met, we try to select a layout with fewer tiles | ||
* (`tiles=columns*rows`) that is within the constraint. | ||
*/ | ||
minHeight?: number; | ||
/** | ||
* For which orientation the layout definition should be applied. | ||
* Will be used for both landscape and portrait if no value is specified. | ||
*/ | ||
orientation?: 'landscape' | 'portrait'; | ||
}; | ||
export type GridLayoutInfo = { | ||
/** Layout name (convention `<column_count>x<row_count>`). */ | ||
@@ -8,4 +36,2 @@ name: string; | ||
rows: number; | ||
/** Minimum number of tiles needed to use this layout. */ | ||
minTiles: number; | ||
/** Maximum tiles that fit into this layout. */ | ||
@@ -17,5 +43,10 @@ maxTiles: number; | ||
minHeight: number; | ||
orientation?: 'landscape' | 'portrait'; | ||
}; | ||
export declare const GRID_LAYOUTS: GridLayoutDefinition[]; | ||
export declare function selectGridLayout(layouts: GridLayoutDefinition[], participantCount: number, width: number, height: number): GridLayoutDefinition; | ||
export declare function selectGridLayout(layoutDefinitions: GridLayoutDefinition[], participantCount: number, width: number, height: number): GridLayoutInfo; | ||
/** | ||
* @internal | ||
*/ | ||
export declare function expandAndSortLayoutDefinitions(layouts: GridLayoutDefinition[]): GridLayoutInfo[]; | ||
//# sourceMappingURL=grid-layouts.d.ts.map |
@@ -7,3 +7,3 @@ export * from './detectMobileBrowser'; | ||
export * from './eventGroups'; | ||
export { selectGridLayout, GRID_LAYOUTS, type GridLayoutDefinition } from './grid-layouts'; | ||
export { selectGridLayout, GRID_LAYOUTS, type GridLayoutDefinition, type GridLayoutInfo, } from './grid-layouts'; | ||
export { setDifference } from './set-helper'; | ||
@@ -10,0 +10,0 @@ export { supportsScreenSharing } from './featureDetection'; |
{ | ||
"name": "@livekit/components-core", | ||
"version": "0.11.0", | ||
"version": "0.11.1", | ||
"license": "Apache-2.0", | ||
@@ -28,3 +28,3 @@ "author": "LiveKit", | ||
"dependencies": { | ||
"@floating-ui/dom": "1.6.7", | ||
"@floating-ui/dom": "1.6.8", | ||
"loglevel": "1.9.1", | ||
@@ -31,0 +31,0 @@ "rxjs": "7.8.1" |
import { describe, test, expect } from 'vitest'; | ||
import type { GridLayoutDefinition } from './grid-layouts'; | ||
import { GRID_LAYOUTS, selectGridLayout } from './grid-layouts'; | ||
import { GRID_LAYOUTS, expandAndSortLayoutDefinitions, selectGridLayout } from './grid-layouts'; | ||
@@ -13,6 +12,9 @@ describe.concurrent('Test correct layout for participant count with no screen size limits:', () => { | ||
); | ||
test.each([{ participantCount: 2, expected: '2x1' }])( | ||
'Layout for $participantCount should be -> $expected', | ||
({ participantCount, expected }) => { | ||
const layout = selectGridLayout(GRID_LAYOUTS, participantCount, 9999, 9999); | ||
test.each([ | ||
{ participantCount: 2, expected: '1x2', width: 720 }, | ||
{ participantCount: 2, expected: '2x1', width: 1920 }, | ||
])( | ||
'Layout for $participantCount for $width x 720 should be -> $expected', | ||
({ participantCount, expected, width }) => { | ||
const layout = selectGridLayout(GRID_LAYOUTS, participantCount, width, 1080); | ||
expect(layout.name).toBe(expected); | ||
@@ -52,15 +54,72 @@ }, | ||
function is_same(array1: GridLayoutDefinition[], array2: GridLayoutDefinition[]) { | ||
return ( | ||
array1.length == array2.length && | ||
array1.every((element, index) => { | ||
return element === array2[index]; | ||
}) | ||
); | ||
} | ||
describe.concurrent('Test defined GRID_LAYOUTS array is valid', () => { | ||
describe('Test defined GRID_LAYOUTS array is valid', () => { | ||
test('GRID_LAYOUTS should be ordered by layout.maxParticipants', () => { | ||
const layouts = [...GRID_LAYOUTS]; | ||
layouts.sort((a, b) => a.maxTiles - b.maxTiles); | ||
expect(is_same(layouts, GRID_LAYOUTS)).toBe(true); | ||
const layouts = expandAndSortLayoutDefinitions(GRID_LAYOUTS); | ||
expect(layouts).toMatchInlineSnapshot(` | ||
[ | ||
{ | ||
"columns": 1, | ||
"maxTiles": 1, | ||
"minHeight": 0, | ||
"minWidth": 0, | ||
"name": "1x1", | ||
"orientation": undefined, | ||
"rows": 1, | ||
}, | ||
{ | ||
"columns": 1, | ||
"maxTiles": 2, | ||
"minHeight": 0, | ||
"minWidth": 0, | ||
"name": "1x2", | ||
"orientation": "portrait", | ||
"rows": 2, | ||
}, | ||
{ | ||
"columns": 2, | ||
"maxTiles": 2, | ||
"minHeight": 0, | ||
"minWidth": 0, | ||
"name": "2x1", | ||
"orientation": "landscape", | ||
"rows": 1, | ||
}, | ||
{ | ||
"columns": 2, | ||
"maxTiles": 4, | ||
"minHeight": 0, | ||
"minWidth": 560, | ||
"name": "2x2", | ||
"orientation": undefined, | ||
"rows": 2, | ||
}, | ||
{ | ||
"columns": 3, | ||
"maxTiles": 9, | ||
"minHeight": 0, | ||
"minWidth": 700, | ||
"name": "3x3", | ||
"orientation": undefined, | ||
"rows": 3, | ||
}, | ||
{ | ||
"columns": 4, | ||
"maxTiles": 16, | ||
"minHeight": 0, | ||
"minWidth": 960, | ||
"name": "4x4", | ||
"orientation": undefined, | ||
"rows": 4, | ||
}, | ||
{ | ||
"columns": 5, | ||
"maxTiles": 25, | ||
"minHeight": 0, | ||
"minWidth": 1100, | ||
"name": "5x5", | ||
"orientation": undefined, | ||
"rows": 5, | ||
}, | ||
] | ||
`); | ||
}); | ||
@@ -73,10 +132,11 @@ }); | ||
test.each([ | ||
{ desiredLayoutName: '5x5', expected: '4x4' }, | ||
{ desiredLayoutName: '4x4', expected: '3x3' }, | ||
{ desiredLayoutName: '3x3', expected: '2x2' }, | ||
{ desiredLayoutName: '2x1', expected: '1x2' }, | ||
{ desiredGrid: { columns: 4, rows: 4 }, expected: { columns: 3, rows: 3 } }, | ||
{ desiredGrid: { columns: 5, rows: 5 }, expected: { columns: 4, rows: 4 } }, | ||
{ desiredGrid: { columns: 3, rows: 3 }, expected: { columns: 2, rows: 2 } }, | ||
])( | ||
'If the minimum width for the $desiredLayoutName layout is not satisfied switch to smaller layout ($expected).', | ||
({ desiredLayoutName, expected }) => { | ||
const desiredLayout = GRID_LAYOUTS.find((layout_) => layout_.name === desiredLayoutName); | ||
'If the minimum width for the $desiredGrid layout is not satisfied, switch to smaller layout ($expected).', | ||
({ desiredGrid, expected }) => { | ||
const desiredLayout = expandAndSortLayoutDefinitions(GRID_LAYOUTS).find( | ||
(layout_) => layout_.columns === desiredGrid.columns && layout_.rows === desiredGrid.rows, | ||
); | ||
if (desiredLayout === undefined) throw new Error('Could not find the desired layout.'); | ||
@@ -91,3 +151,4 @@ | ||
); | ||
expect(layout.name).toBe(expected); | ||
expect(layout.columns).toBe(expected.columns); | ||
expect(layout.rows).toBe(expected.rows); | ||
}, | ||
@@ -94,0 +155,0 @@ ); |
import { log } from '../logger'; | ||
/** | ||
* @public | ||
*/ | ||
export type GridLayoutDefinition = { | ||
/** Column count of the grid layout. */ | ||
columns: number; | ||
/** Row count of the grid layout. */ | ||
rows: number; | ||
// # Constraints that have to be meet to use this layout. | ||
/** | ||
* Minimum grid container width required to use this layout. | ||
* @remarks | ||
* If this constraint is not met, we try to select a layout with fewer tiles | ||
* (`tiles=columns*rows`) that is within the constraint. | ||
*/ | ||
minWidth?: number; | ||
/** | ||
* Minimum grid container height required to use this layout. | ||
* @remarks | ||
* If this constraint is not met, we try to select a layout with fewer tiles | ||
* (`tiles=columns*rows`) that is within the constraint. | ||
*/ | ||
minHeight?: number; | ||
/** | ||
* For which orientation the layout definition should be applied. | ||
* Will be used for both landscape and portrait if no value is specified. | ||
*/ | ||
orientation?: 'landscape' | 'portrait'; | ||
}; | ||
export type GridLayoutInfo = { | ||
/** Layout name (convention `<column_count>x<row_count>`). */ | ||
@@ -12,4 +42,2 @@ name: string; | ||
// ## 1. Participant range: | ||
/** Minimum number of tiles needed to use this layout. */ | ||
minTiles: number; | ||
/** Maximum tiles that fit into this layout. */ | ||
@@ -22,2 +50,3 @@ maxTiles: number; | ||
minHeight: number; | ||
orientation?: 'landscape' | 'portrait'; | ||
}; | ||
@@ -29,7 +58,2 @@ | ||
rows: 1, | ||
name: '1x1', | ||
minTiles: 1, | ||
maxTiles: 1, | ||
minWidth: 0, | ||
minHeight: 0, | ||
}, | ||
@@ -39,7 +63,3 @@ { | ||
rows: 2, | ||
name: '1x2', | ||
minTiles: 2, | ||
maxTiles: 2, | ||
minWidth: 0, | ||
minHeight: 0, | ||
orientation: 'portrait', | ||
}, | ||
@@ -49,7 +69,3 @@ { | ||
rows: 1, | ||
name: '2x1', | ||
minTiles: 2, | ||
maxTiles: 2, | ||
minWidth: 900, | ||
minHeight: 0, | ||
orientation: 'landscape', | ||
}, | ||
@@ -59,7 +75,3 @@ { | ||
rows: 2, | ||
name: '2x2', | ||
minTiles: 3, | ||
maxTiles: 4, | ||
minWidth: 560, | ||
minHeight: 0, | ||
}, | ||
@@ -69,7 +81,3 @@ { | ||
rows: 3, | ||
name: '3x3', | ||
minTiles: 5, | ||
maxTiles: 9, | ||
minWidth: 700, | ||
minHeight: 0, | ||
}, | ||
@@ -79,7 +87,3 @@ { | ||
rows: 4, | ||
name: '4x4', | ||
minTiles: 10, | ||
maxTiles: 16, | ||
minWidth: 960, | ||
minHeight: 0, | ||
}, | ||
@@ -89,18 +93,22 @@ { | ||
rows: 5, | ||
name: '5x5', | ||
minTiles: 17, | ||
maxTiles: 25, | ||
minWidth: 1100, | ||
minHeight: 0, | ||
}, | ||
]; | ||
] as const; | ||
export function selectGridLayout( | ||
layouts: GridLayoutDefinition[], | ||
layoutDefinitions: GridLayoutDefinition[], | ||
participantCount: number, | ||
width: number, | ||
height: number, | ||
): GridLayoutDefinition { | ||
): GridLayoutInfo { | ||
if (layoutDefinitions.length < 1) { | ||
throw new Error('At least one grid layout definition must be provided.'); | ||
} | ||
const layouts = expandAndSortLayoutDefinitions(layoutDefinitions); | ||
if (width <= 0 || height <= 0) { | ||
return layouts[0]; | ||
} | ||
// Find the best layout to fit all participants. | ||
let currentLayoutIndex = 0; | ||
const containerOrientation = width / height > 1 ? 'landscape' : 'portrait'; | ||
let layout = layouts.find((layout_, index, allLayouts) => { | ||
@@ -110,5 +118,6 @@ currentLayoutIndex = index; | ||
allLayouts.findIndex((l, i) => { | ||
const fitsOrientation = !l.orientation || l.orientation === containerOrientation; | ||
const layoutIsBiggerThanCurrent = i > index; | ||
const layoutFitsSameAmountOfParticipants = l.maxTiles === layout_.maxTiles; | ||
return layoutIsBiggerThanCurrent && layoutFitsSameAmountOfParticipants; | ||
return layoutIsBiggerThanCurrent && layoutFitsSameAmountOfParticipants && fitsOrientation; | ||
}) !== -1; | ||
@@ -121,3 +130,3 @@ return layout_.maxTiles >= participantCount && !isBiggerLayoutAvailable; | ||
log.warn( | ||
`No layout found for: participantCount: ${participantCount}, width/height: ${width}/${height} fallback to biggest available layout (${layout.name}).`, | ||
`No layout found for: participantCount: ${participantCount}, width/height: ${width}/${height} fallback to biggest available layout (${layout}).`, | ||
); | ||
@@ -144,1 +153,30 @@ } else { | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
export function expandAndSortLayoutDefinitions(layouts: GridLayoutDefinition[]): GridLayoutInfo[] { | ||
return [...layouts] | ||
.map((layout) => { | ||
return { | ||
name: `${layout.columns}x${layout.rows}`, | ||
columns: layout.columns, | ||
rows: layout.rows, | ||
maxTiles: layout.columns * layout.rows, | ||
minWidth: layout.minWidth ?? 0, | ||
minHeight: layout.minHeight ?? 0, | ||
orientation: layout.orientation, | ||
} satisfies GridLayoutInfo; | ||
}) | ||
.sort((a, b) => { | ||
if (a.maxTiles !== b.maxTiles) { | ||
return a.maxTiles - b.maxTiles; | ||
} else if (a.minWidth !== 0 || b.minWidth !== 0) { | ||
return a.minWidth - b.minWidth; | ||
} else if (a.minHeight !== 0 || b.minHeight !== 0) { | ||
return a.minHeight - b.minHeight; | ||
} else { | ||
return 0; | ||
} | ||
}); | ||
} |
@@ -7,5 +7,10 @@ export * from './detectMobileBrowser'; | ||
export * from './eventGroups'; | ||
export { selectGridLayout, GRID_LAYOUTS, type GridLayoutDefinition } from './grid-layouts'; | ||
export { | ||
selectGridLayout, | ||
GRID_LAYOUTS, | ||
type GridLayoutDefinition, | ||
type GridLayoutInfo, | ||
} from './grid-layouts'; | ||
export { setDifference } from './set-helper'; | ||
export { supportsScreenSharing } from './featureDetection'; | ||
export * from './transcriptions'; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
669036
9176
+ Added@floating-ui/dom@1.6.8(transitive)
+ Added@livekit/protocol@1.33.0(transitive)
+ Addedlivekit-client@2.9.0(transitive)
- Removed@floating-ui/dom@1.6.7(transitive)
- Removed@livekit/protocol@1.30.01.32.1(transitive)
- Removedlivekit-client@2.8.1(transitive)
Updated@floating-ui/dom@1.6.8