UNPKG

@livekit/components-core

Version:

This package is a wrapper around the [livekit/client-sdk-js](https://github.com/livekit/client-sdk-js) package. It transforms the event based logic into simple to use observable state and component level APIs. This package is the core for all framework sp

171 lines (164 loc) 4.92 kB
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>`). */ name: string; /** Column count of the layout. */ columns: number; /** Row count of the layout. */ rows: number; // # Constraints that have to be meet to use this layout. // ## 1. Participant range: /** Maximum tiles that fit into this layout. */ maxTiles: number; // ## 2. Screen size limits: /** Minimum width required to use this layout. */ minWidth: number; /** Minimum height required to use this layout. */ minHeight: number; orientation?: 'landscape' | 'portrait'; }; export const GRID_LAYOUTS: GridLayoutDefinition[] = [ { columns: 1, rows: 1, }, { columns: 1, rows: 2, orientation: 'portrait', }, { columns: 2, rows: 1, orientation: 'landscape', }, { columns: 2, rows: 2, minWidth: 560, }, { columns: 3, rows: 3, minWidth: 700, }, { columns: 4, rows: 4, minWidth: 960, }, { columns: 5, rows: 5, minWidth: 1100, }, ] as const; export function selectGridLayout( layoutDefinitions: GridLayoutDefinition[], participantCount: number, width: number, height: number, ): 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) => { currentLayoutIndex = index; const isBiggerLayoutAvailable = allLayouts.findIndex((l, i) => { const fitsOrientation = !l.orientation || l.orientation === containerOrientation; const layoutIsBiggerThanCurrent = i > index; const layoutFitsSameAmountOfParticipants = l.maxTiles === layout_.maxTiles; return layoutIsBiggerThanCurrent && layoutFitsSameAmountOfParticipants && fitsOrientation; }) !== -1; return layout_.maxTiles >= participantCount && !isBiggerLayoutAvailable; }); if (layout === undefined) { layout = layouts[layouts.length - 1]; if (layout) { log.warn( `No layout found for: participantCount: ${participantCount}, width/height: ${width}/${height} fallback to biggest available layout (${layout}).`, ); } else { throw new Error(`No layout or fallback layout found.`); } } // Check if the layout fits into the screen constraints. If not, recursively check the next smaller layout. if (width < layout.minWidth || height < layout.minHeight) { // const currentLayoutIndex = layouts.indexOf(layout); if (currentLayoutIndex > 0) { const smallerLayout = layouts[currentLayoutIndex - 1]; layout = selectGridLayout( layouts.slice(0, currentLayoutIndex), smallerLayout.maxTiles, width, height, ); } } return layout; } /** * @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; } }); }