mirror of https://github.com/OpenVidu/openvidu.git
ov-components: enhance layout flexibility with additional element slots and improved rendering logic
parent
ff492a1f22
commit
3c02121ebe
|
|
@ -1,5 +1,10 @@
|
||||||
<div class="container" [ngClass]="{ withCaptions: captionsEnabled, withMargin: localParticipant.isMinimized }">
|
<div class="container" [ngClass]="{ withCaptions: captionsEnabled, withMargin: localParticipant.isMinimized }">
|
||||||
<div id="layout" class="layout" #layout>
|
<div id="layout" class="layout" #layout>
|
||||||
|
<!-- Top slot: Render elements that should appear at the top -->
|
||||||
|
@if (layoutAdditionalElementsTemplate && templateConfig.layoutAdditionalElementsSlot === 'top') {
|
||||||
|
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
|
||||||
|
}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
#localLayoutElement
|
#localLayoutElement
|
||||||
*ngFor="let track of localParticipant.tracks; trackBy: trackParticipantElement"
|
*ngFor="let track of localParticipant.tracks; trackBy: trackParticipantElement"
|
||||||
|
|
@ -21,8 +26,8 @@
|
||||||
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
|
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Render additional layout elements injected via ovAdditionalLayoutElement -->
|
<!-- Default slot: Render additional layout elements (backward compatibility and default position) -->
|
||||||
@if (layoutAdditionalElementsTemplate) {
|
@if (layoutAdditionalElementsTemplate && (templateConfig.layoutAdditionalElementsSlot === 'default' || !templateConfig.layoutAdditionalElementsSlot)) {
|
||||||
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
|
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,6 +45,11 @@
|
||||||
>
|
>
|
||||||
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
|
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom slot: Render elements that should appear at the bottom -->
|
||||||
|
@if (layoutAdditionalElementsTemplate && templateConfig.layoutAdditionalElementsSlot === 'bottom') {
|
||||||
|
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <ov-captions *ngIf="captionsEnabled" class="OV_ignored"></ov-captions> -->
|
<!-- <ov-captions *ngIf="captionsEnabled" class="OV_ignored"></ov-captions> -->
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { LayoutAdditionalElementsDirective } from '../../directives/template/internals.directive';
|
import { LayoutAdditionalElementsDirective } from '../../directives/template/internals.directive';
|
||||||
|
|
||||||
|
import { CdkDrag } from '@angular/cdk/drag-drop';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
|
|
@ -15,13 +16,12 @@ import {
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { combineLatest, map, Subject, takeUntil } from 'rxjs';
|
import { combineLatest, map, Subject, takeUntil } from 'rxjs';
|
||||||
import { StreamDirective } from '../../directives/template/openvidu-components-angular.directive';
|
import { StreamDirective } from '../../directives/template/openvidu-components-angular.directive';
|
||||||
import { ParticipantTrackPublication, ParticipantModel } from '../../models/participant.model';
|
import { ParticipantModel, ParticipantTrackPublication } from '../../models/participant.model';
|
||||||
import { LayoutService } from '../../services/layout/layout.service';
|
|
||||||
import { ParticipantService } from '../../services/participant/participant.service';
|
|
||||||
import { CdkDrag } from '@angular/cdk/drag-drop';
|
|
||||||
import { PanelService } from '../../services/panel/panel.service';
|
|
||||||
import { GlobalConfigService } from '../../services/config/global-config.service';
|
|
||||||
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
||||||
|
import { GlobalConfigService } from '../../services/config/global-config.service';
|
||||||
|
import { LayoutService } from '../../services/layout/layout.service';
|
||||||
|
import { PanelService } from '../../services/panel/panel.service';
|
||||||
|
import { ParticipantService } from '../../services/participant/participant.service';
|
||||||
import { LayoutTemplateConfiguration, TemplateManagerService } from '../../services/template/template-manager.service';
|
import { LayoutTemplateConfiguration, TemplateManagerService } from '../../services/template/template-manager.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[ovPreJoin]',
|
selector: '[ovPreJoin]',
|
||||||
|
|
@ -253,14 +253,27 @@ export class LeaveButtonDirective {
|
||||||
* as additional layout elements within the videoconference UI.
|
* as additional layout elements within the videoconference UI.
|
||||||
* This enables you to extend the layout with extra controls, banners, or any custom UI.
|
* This enables you to extend the layout with extra controls, banners, or any custom UI.
|
||||||
*
|
*
|
||||||
* Usage example:
|
* You can specify a slot to control where the element is positioned:
|
||||||
|
* - 'top': Position at the top of the layout (after local participant, before remote participants)
|
||||||
|
* - 'bottom': Position at the bottom of the layout (after all participants)
|
||||||
|
* - 'default' or no slot: Position after local participant (default behavior)
|
||||||
|
*
|
||||||
|
* Usage examples:
|
||||||
* ```html
|
* ```html
|
||||||
* <ov-videoconference>
|
* <ov-videoconference>
|
||||||
|
* <!-- Default position (after local participant) -->
|
||||||
* <ng-container *ovLayoutAdditionalElements>
|
* <ng-container *ovLayoutAdditionalElements>
|
||||||
* <div class="my-custom-layout-element">
|
* <div class="my-banner">Banner</div>
|
||||||
* <!-- Your custom HTML here -->
|
* </ng-container>
|
||||||
* <span>Extra layout element</span>
|
*
|
||||||
* </div>
|
* <!-- Top position -->
|
||||||
|
* <ng-container *ovLayoutAdditionalElements="'top'">
|
||||||
|
* <div class="top-bar">Top Bar</div>
|
||||||
|
* </ng-container>
|
||||||
|
*
|
||||||
|
* <!-- Bottom position -->
|
||||||
|
* <ng-container *ovLayoutAdditionalElements="'bottom'">
|
||||||
|
* <div class="bottom-info">Footer Info</div>
|
||||||
* </ng-container>
|
* </ng-container>
|
||||||
* </ov-videoconference>
|
* </ov-videoconference>
|
||||||
* ```
|
* ```
|
||||||
|
|
@ -270,10 +283,27 @@ export class LeaveButtonDirective {
|
||||||
standalone: false
|
standalone: false
|
||||||
})
|
})
|
||||||
export class LayoutAdditionalElementsDirective {
|
export class LayoutAdditionalElementsDirective {
|
||||||
|
/**
|
||||||
|
* Slot position: 'top', 'bottom', or 'default'
|
||||||
|
*/
|
||||||
|
slot: 'top' | 'bottom' | 'default' = 'default';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public template: TemplateRef<any>,
|
public template: TemplateRef<any>,
|
||||||
public container: ViewContainerRef
|
public container: ViewContainerRef
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
@Input('ovLayoutAdditionalElements')
|
||||||
|
set ovLayoutAdditionalElements(slot: 'top' | 'bottom' | 'default' | '') {
|
||||||
|
if (slot === 'top' || slot === 'bottom' || slot === 'default') {
|
||||||
|
this.slot = slot;
|
||||||
|
} else {
|
||||||
|
this.slot = 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export enum LayoutClass {
|
||||||
ROOT_ELEMENT = 'OV_root',
|
ROOT_ELEMENT = 'OV_root',
|
||||||
BIG_ELEMENT = 'OV_big',
|
BIG_ELEMENT = 'OV_big',
|
||||||
SMALL_ELEMENT = 'OV_small',
|
SMALL_ELEMENT = 'OV_small',
|
||||||
|
TOP_BAR_ELEMENT = 'OV_top-bar',
|
||||||
IGNORED_ELEMENT = 'OV_ignored',
|
IGNORED_ELEMENT = 'OV_ignored',
|
||||||
MINIMIZED_ELEMENT = 'OV_minimized',
|
MINIMIZED_ELEMENT = 'OV_minimized',
|
||||||
SIDENAV_CONTAINER = 'sidenav-container',
|
SIDENAV_CONTAINER = 'sidenav-container',
|
||||||
|
|
@ -42,6 +43,7 @@ export interface ElementDimensions {
|
||||||
width: number;
|
width: number;
|
||||||
big?: boolean;
|
big?: boolean;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
|
topBar?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -200,7 +202,8 @@ export class OpenViduLayout {
|
||||||
const elements = children.map((element) => {
|
const elements = children.map((element) => {
|
||||||
const res = this.getChildDims(element);
|
const res = this.getChildDims(element);
|
||||||
res.big = element.classList.contains(this.opts.bigClass);
|
res.big = element.classList.contains(this.opts.bigClass);
|
||||||
res.small = element.classList.contains(this.opts.smallClass);
|
res.small = element.classList.contains(LayoutClass.SMALL_ELEMENT);
|
||||||
|
res.topBar = element.classList.contains(LayoutClass.TOP_BAR_ELEMENT);
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -545,13 +548,15 @@ export class OpenViduLayout {
|
||||||
let bigOffsetLeft = 0;
|
let bigOffsetLeft = 0;
|
||||||
const bigIndices: number[] = [];
|
const bigIndices: number[] = [];
|
||||||
const smallIndices: number[] = [];
|
const smallIndices: number[] = [];
|
||||||
|
const topBarIndices: number[] = [];
|
||||||
const normalIndices: number[] = [];
|
const normalIndices: number[] = [];
|
||||||
let bigBoxes: LayoutBox[] = [];
|
let bigBoxes: LayoutBox[] = [];
|
||||||
let smallBoxes: LayoutBox[] = [];
|
let smallBoxes: LayoutBox[] = [];
|
||||||
|
let topBarBoxes: LayoutBox[] = [];
|
||||||
let normalBoxes: LayoutBox[] = [];
|
let normalBoxes: LayoutBox[] = [];
|
||||||
let areas: { big: LayoutArea | null; normal: LayoutArea | null; small: LayoutArea | null } = { big: null, normal: null, small: null };
|
let areas: { big: LayoutArea | null; normal: LayoutArea | null; small: LayoutArea | null; topBar: LayoutArea | null } = { big: null, normal: null, small: null, topBar: null };
|
||||||
|
|
||||||
// Separate elements into three categories: big, small, and normal
|
// Separate elements into categories: big, small, topbar, and normal
|
||||||
const bigOnes = elements.filter((element, idx) => {
|
const bigOnes = elements.filter((element, idx) => {
|
||||||
if (element.big) {
|
if (element.big) {
|
||||||
bigIndices.push(idx);
|
bigIndices.push(idx);
|
||||||
|
|
@ -559,23 +564,31 @@ export class OpenViduLayout {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
const topBarOnes = elements.filter((element, idx) => {
|
||||||
|
if (!element.big && element.topBar) {
|
||||||
|
topBarIndices.push(idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
const smallOnes = elements.filter((element, idx) => {
|
const smallOnes = elements.filter((element, idx) => {
|
||||||
if (!element.big && element.small) {
|
if (!element.big && !element.topBar && element.small) {
|
||||||
smallIndices.push(idx);
|
smallIndices.push(idx);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
const normalOnes = elements.filter((element, idx) => {
|
const normalOnes = elements.filter((element, idx) => {
|
||||||
if (!element.big && !element.small) {
|
if (!element.big && !element.topBar && !element.small) {
|
||||||
normalIndices.push(idx);
|
normalIndices.push(idx);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle different layout scenarios based on element types
|
// Handle different layout scenarios based on element types
|
||||||
if (bigOnes.length > 0 && (normalOnes.length > 0 || smallOnes.length > 0)) {
|
if (bigOnes.length > 0 && (normalOnes.length > 0 || smallOnes.length > 0 || topBarOnes.length > 0)) {
|
||||||
// Scenario: Big elements with normal/small elements
|
// Scenario: Big elements with normal/small/topbar elements
|
||||||
let bigWidth;
|
let bigWidth;
|
||||||
let bigHeight;
|
let bigHeight;
|
||||||
let showBigFirst = bigFirst;
|
let showBigFirst = bigFirst;
|
||||||
|
|
@ -622,7 +635,7 @@ export class OpenViduLayout {
|
||||||
maxRatio,
|
maxRatio,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
containerHeight - bigHeight,
|
containerHeight - bigHeight,
|
||||||
normalOnes.length + smallOnes.length,
|
normalOnes.length + smallOnes.length + topBarOnes.length,
|
||||||
smallMaxWidth,
|
smallMaxWidth,
|
||||||
smallMaxHeight
|
smallMaxHeight
|
||||||
);
|
);
|
||||||
|
|
@ -677,7 +690,7 @@ export class OpenViduLayout {
|
||||||
maxRatio,
|
maxRatio,
|
||||||
containerWidth - bigWidth,
|
containerWidth - bigWidth,
|
||||||
containerHeight,
|
containerHeight,
|
||||||
normalOnes.length + smallOnes.length,
|
normalOnes.length + smallOnes.length + topBarOnes.length,
|
||||||
smallMaxWidth,
|
smallMaxWidth,
|
||||||
smallMaxHeight
|
smallMaxHeight
|
||||||
);
|
);
|
||||||
|
|
@ -718,16 +731,16 @@ export class OpenViduLayout {
|
||||||
height: containerHeight - offsetTop
|
height: containerHeight - offsetTop
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (bigOnes.length > 0 && smallOnes.length === 0) {
|
} else if (bigOnes.length > 0 && normalOnes.length === 0 && smallOnes.length === 0 && topBarOnes.length === 0) {
|
||||||
// We only have one bigOne just center it
|
// We only have bigOnes just center it
|
||||||
areas.big = {
|
areas.big = {
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: containerWidth,
|
width: containerWidth,
|
||||||
height: containerHeight
|
height: containerHeight
|
||||||
};
|
};
|
||||||
} else if (normalOnes.length > 0 || smallOnes.length > 0) {
|
} else if (normalOnes.length > 0 || smallOnes.length > 0 || topBarOnes.length > 0) {
|
||||||
// Only normal and/or small elements
|
// Only normal, small, and/or topbar elements
|
||||||
areas.normal = {
|
areas.normal = {
|
||||||
top: offsetTop,
|
top: offsetTop,
|
||||||
left: offsetLeft,
|
left: offsetLeft,
|
||||||
|
|
@ -755,75 +768,67 @@ export class OpenViduLayout {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (areas.normal) {
|
if (areas.normal) {
|
||||||
// Calculate equivalent "normal-sized" count considering small elements take less space
|
let currentTop = areas.normal.top;
|
||||||
// Treat each small element as taking up a fraction of a normal element's space
|
let remainingHeight = areas.normal.height;
|
||||||
const smallElementSpaceFactor = 0.25; // Small elements take ~25% of normal element space
|
|
||||||
const equivalentNormalCount = normalOnes.length + (smallOnes.length * smallElementSpaceFactor);
|
|
||||||
|
|
||||||
// Calculate layout for all elements together as if they were normal-sized
|
// 1. Position TopBar Elements at the very top (header style: full width, 80px height)
|
||||||
const allNormalAreaElements = [...normalOnes, ...smallOnes];
|
if (topBarOnes.length > 0) {
|
||||||
|
const topBarHeight = 80;
|
||||||
|
const topBarWidth = Math.floor(containerWidth / topBarOnes.length);
|
||||||
|
|
||||||
if (allNormalAreaElements.length > 0) {
|
topBarBoxes = topBarOnes.map((element, idx) => {
|
||||||
// Get dimensions as if all elements were normal-sized
|
|
||||||
const allBoxes = this.getLayoutAux(
|
|
||||||
{
|
|
||||||
containerWidth: areas.normal.width,
|
|
||||||
containerHeight: areas.normal.height,
|
|
||||||
offsetLeft: areas.normal.left,
|
|
||||||
offsetTop: areas.normal.top,
|
|
||||||
fixedRatio,
|
|
||||||
minRatio,
|
|
||||||
maxRatio,
|
|
||||||
alignItems: areas.big ? smallAlignItems : alignItems,
|
|
||||||
maxWidth: areas.big ? maxWidth : maxWidth,
|
|
||||||
maxHeight: areas.big ? maxHeight : maxHeight,
|
|
||||||
scaleLastRow
|
|
||||||
},
|
|
||||||
allNormalAreaElements
|
|
||||||
);
|
|
||||||
|
|
||||||
// Split boxes and adjust small elements
|
|
||||||
normalBoxes = allBoxes.slice(0, normalOnes.length);
|
|
||||||
const rawSmallBoxes = allBoxes.slice(normalOnes.length);
|
|
||||||
|
|
||||||
// Adjust small element boxes to use restricted dimensions and reposition them
|
|
||||||
// to utilize space efficiently
|
|
||||||
smallBoxes = rawSmallBoxes.map((box, idx) => {
|
|
||||||
// Calculate restricted size while maintaining aspect ratio
|
|
||||||
const restrictedWidth = Math.min(box.width, smallMaxWidth);
|
|
||||||
const restrictedHeight = Math.min(box.height, smallMaxHeight);
|
|
||||||
|
|
||||||
// Maintain the position but adjust size
|
|
||||||
return {
|
return {
|
||||||
left: box.left,
|
left: areas.normal!.left + idx * topBarWidth,
|
||||||
top: box.top,
|
top: currentTop,
|
||||||
width: restrictedWidth,
|
width: topBarWidth,
|
||||||
height: restrictedHeight
|
height: topBarHeight
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there are small elements, try to compact them and redistribute space
|
currentTop += topBarHeight;
|
||||||
if (smallOnes.length > 0 && normalOnes.length > 0) {
|
remainingHeight -= topBarHeight;
|
||||||
// Recalculate normal elements with more space since small elements take less
|
}
|
||||||
const adjustedDimensions = this.getBestDimensions(
|
|
||||||
minRatio,
|
|
||||||
maxRatio,
|
|
||||||
areas.normal.width,
|
|
||||||
areas.normal.height,
|
|
||||||
equivalentNormalCount,
|
|
||||||
areas.big ? maxWidth : maxWidth,
|
|
||||||
areas.big ? maxHeight : maxHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the adjusted dimensions give us bigger normal elements, recalculate
|
// 2. Position Small Elements (reduced format)
|
||||||
if (normalBoxes.length > 0 &&
|
if (smallOnes.length > 0) {
|
||||||
adjustedDimensions.targetHeight > normalBoxes[0].height) {
|
const maxSmallWidthAvailable = smallMaxWidth;
|
||||||
|
const maxSmallHeightAvailable = smallMaxHeight;
|
||||||
|
|
||||||
|
const tentativeCols = maxSmallWidthAvailable === Infinity
|
||||||
|
? smallOnes.length
|
||||||
|
: Math.max(1, Math.floor(containerWidth / maxSmallWidthAvailable));
|
||||||
|
const displayCols = Math.max(1, Math.min(smallOnes.length, tentativeCols));
|
||||||
|
|
||||||
|
const computedWidth = maxSmallWidthAvailable === Infinity
|
||||||
|
? Math.floor(containerWidth / displayCols)
|
||||||
|
: maxSmallWidthAvailable;
|
||||||
|
const computedHeight = maxSmallHeightAvailable === Infinity ? computedWidth : maxSmallHeightAvailable;
|
||||||
|
|
||||||
|
const rowWidth = displayCols * computedWidth;
|
||||||
|
const rowOffset = Math.floor(Math.max(0, containerWidth - rowWidth) / 2);
|
||||||
|
|
||||||
|
smallBoxes = smallOnes.map((element, idx) => {
|
||||||
|
const col = idx % displayCols;
|
||||||
|
return {
|
||||||
|
left: areas.normal!.left + col * computedWidth + rowOffset,
|
||||||
|
top: currentTop,
|
||||||
|
width: computedWidth,
|
||||||
|
height: computedHeight
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
currentTop += computedHeight;
|
||||||
|
remainingHeight -= computedHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Position Normal Elements in remaining space
|
||||||
|
if (normalOnes.length > 0) {
|
||||||
normalBoxes = this.getLayoutAux(
|
normalBoxes = this.getLayoutAux(
|
||||||
{
|
{
|
||||||
containerWidth: areas.normal.width,
|
containerWidth: areas.normal.width,
|
||||||
containerHeight: areas.normal.height,
|
containerHeight: Math.max(0, remainingHeight),
|
||||||
offsetLeft: areas.normal.left,
|
offsetLeft: areas.normal.left,
|
||||||
offsetTop: areas.normal.top,
|
offsetTop: currentTop,
|
||||||
fixedRatio,
|
fixedRatio,
|
||||||
minRatio,
|
minRatio,
|
||||||
maxRatio,
|
maxRatio,
|
||||||
|
|
@ -834,42 +839,6 @@ export class OpenViduLayout {
|
||||||
},
|
},
|
||||||
normalOnes
|
normalOnes
|
||||||
);
|
);
|
||||||
|
|
||||||
// Position small elements in remaining space (bottom or side)
|
|
||||||
const normalMaxBottom = normalBoxes.length > 0 ? Math.max(...normalBoxes.map(b => b.top + b.height)) : areas.normal.top;
|
|
||||||
const normalMaxRight = normalBoxes.length > 0 ? Math.max(...normalBoxes.map(b => b.left + b.width)) : areas.normal.left;
|
|
||||||
|
|
||||||
// Position small elements at the end of the layout
|
|
||||||
const spaceAtBottom = (areas.normal.top + areas.normal.height) - normalMaxBottom;
|
|
||||||
const spaceAtRight = (areas.normal.left + areas.normal.width) - normalMaxRight;
|
|
||||||
|
|
||||||
let smallStartX = areas.normal.left;
|
|
||||||
let smallStartY = normalMaxBottom;
|
|
||||||
let availableWidth = areas.normal.width;
|
|
||||||
|
|
||||||
// Choose best positioning based on available space
|
|
||||||
if (spaceAtBottom < smallMaxHeight && spaceAtRight >= smallMaxWidth) {
|
|
||||||
// Position to the right
|
|
||||||
smallStartX = normalMaxRight;
|
|
||||||
smallStartY = areas.normal.top;
|
|
||||||
availableWidth = spaceAtRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arrange small elements in a compact grid
|
|
||||||
const smallCols = Math.floor(availableWidth / smallMaxWidth);
|
|
||||||
smallBoxes = smallOnes.map((_, idx) => {
|
|
||||||
const col = idx % Math.max(1, smallCols);
|
|
||||||
const row = Math.floor(idx / Math.max(1, smallCols));
|
|
||||||
|
|
||||||
return {
|
|
||||||
left: smallStartX + (col * smallMaxWidth),
|
|
||||||
top: smallStartY + (row * smallMaxHeight),
|
|
||||||
width: smallMaxWidth,
|
|
||||||
height: smallMaxHeight
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -877,15 +846,22 @@ export class OpenViduLayout {
|
||||||
let bigBoxesIdx = 0;
|
let bigBoxesIdx = 0;
|
||||||
let normalBoxesIdx = 0;
|
let normalBoxesIdx = 0;
|
||||||
let smallBoxesIdx = 0;
|
let smallBoxesIdx = 0;
|
||||||
|
let topBarBoxesIdx = 0;
|
||||||
// Rebuild the array in the right order based on element types
|
// Rebuild the array in the right order based on element types
|
||||||
elements.forEach((element, idx) => {
|
elements.forEach((element, idx) => {
|
||||||
if (bigIndices.indexOf(idx) > -1) {
|
if (bigIndices.indexOf(idx) > -1) {
|
||||||
boxes[idx] = bigBoxes[bigBoxesIdx];
|
boxes[idx] = bigBoxes[bigBoxesIdx];
|
||||||
bigBoxesIdx += 1;
|
bigBoxesIdx += 1;
|
||||||
|
} else if (topBarIndices.indexOf(idx) > -1) {
|
||||||
|
// Element is topbar (header style: full width, limited height)
|
||||||
|
boxes[idx] = topBarBoxes[topBarBoxesIdx];
|
||||||
|
topBarBoxesIdx += 1;
|
||||||
} else if (smallIndices.indexOf(idx) > -1) {
|
} else if (smallIndices.indexOf(idx) > -1) {
|
||||||
|
// Element is small (reduced format)
|
||||||
boxes[idx] = smallBoxes[smallBoxesIdx];
|
boxes[idx] = smallBoxes[smallBoxesIdx];
|
||||||
smallBoxesIdx += 1;
|
smallBoxesIdx += 1;
|
||||||
} else {
|
} else {
|
||||||
|
// Element is normal
|
||||||
boxes[idx] = normalBoxes[normalBoxesIdx];
|
boxes[idx] = normalBoxes[normalBoxesIdx];
|
||||||
normalBoxesIdx += 1;
|
normalBoxesIdx += 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export class LayoutService {
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
maxHeight: Infinity,
|
maxHeight: Infinity,
|
||||||
smallMaxWidth: Infinity,
|
smallMaxWidth: Infinity,
|
||||||
smallMaxHeight: Infinity,
|
smallMaxHeight: 80,
|
||||||
bigMaxWidth: Infinity,
|
bigMaxWidth: Infinity,
|
||||||
bigMaxHeight: Infinity,
|
bigMaxHeight: Infinity,
|
||||||
scaleLastRow: true,
|
scaleLastRow: true,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { Injectable, TemplateRef } from '@angular/core';
|
import { Injectable, TemplateRef } from '@angular/core';
|
||||||
import { ILogger } from '../../models/logger.model';
|
import {
|
||||||
import { LoggerService } from '../logger/logger.service';
|
LayoutAdditionalElementsDirective,
|
||||||
|
LeaveButtonDirective,
|
||||||
|
ParticipantPanelAfterLocalParticipantDirective,
|
||||||
|
PreJoinDirective,
|
||||||
|
SettingsPanelGeneralAdditionalElementsDirective,
|
||||||
|
ToolbarMoreOptionsAdditionalMenuItemsDirective
|
||||||
|
} from '../../directives/template/internals.directive';
|
||||||
import {
|
import {
|
||||||
ActivitiesPanelDirective,
|
ActivitiesPanelDirective,
|
||||||
AdditionalPanelsDirective,
|
AdditionalPanelsDirective,
|
||||||
|
|
@ -15,14 +21,8 @@ import {
|
||||||
ToolbarAdditionalPanelButtonsDirective,
|
ToolbarAdditionalPanelButtonsDirective,
|
||||||
ToolbarDirective
|
ToolbarDirective
|
||||||
} from '../../directives/template/openvidu-components-angular.directive';
|
} from '../../directives/template/openvidu-components-angular.directive';
|
||||||
import {
|
import { ILogger } from '../../models/logger.model';
|
||||||
PreJoinDirective,
|
import { LoggerService } from '../logger/logger.service';
|
||||||
ParticipantPanelAfterLocalParticipantDirective,
|
|
||||||
LayoutAdditionalElementsDirective,
|
|
||||||
LeaveButtonDirective,
|
|
||||||
SettingsPanelGeneralAdditionalElementsDirective,
|
|
||||||
ToolbarMoreOptionsAdditionalMenuItemsDirective
|
|
||||||
} from '../../directives/template/internals.directive';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object for all templates in the videoconference component
|
* Configuration object for all templates in the videoconference component
|
||||||
|
|
@ -89,6 +89,7 @@ export interface ToolbarTemplateConfiguration {
|
||||||
export interface LayoutTemplateConfiguration {
|
export interface LayoutTemplateConfiguration {
|
||||||
layoutStreamTemplate?: TemplateRef<any>;
|
layoutStreamTemplate?: TemplateRef<any>;
|
||||||
layoutAdditionalElementsTemplate?: TemplateRef<any>;
|
layoutAdditionalElementsTemplate?: TemplateRef<any>;
|
||||||
|
layoutAdditionalElementsSlot?: 'top' | 'bottom' | 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -413,7 +414,8 @@ export class TemplateManagerService {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layoutStreamTemplate: externalStream?.template,
|
layoutStreamTemplate: externalStream?.template,
|
||||||
layoutAdditionalElementsTemplate: externalLayoutAdditionalElements?.template
|
layoutAdditionalElementsTemplate: externalLayoutAdditionalElements?.template,
|
||||||
|
layoutAdditionalElementsSlot: externalLayoutAdditionalElements?.slot || 'default'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Components
|
// Components
|
||||||
export * from './lib/admin/admin-dashboard/admin-dashboard.component';
|
export * from './lib/admin/admin-dashboard/admin-dashboard.component';
|
||||||
export * from './lib/admin/admin-login/admin-login.component';
|
export * from './lib/admin/admin-login/admin-login.component';
|
||||||
|
export * from './lib/components/landscape-warning/landscape-warning.component';
|
||||||
export * from './lib/components/layout/layout.component';
|
export * from './lib/components/layout/layout.component';
|
||||||
export * from './lib/components/panel/activities-panel/activities-panel.component';
|
export * from './lib/components/panel/activities-panel/activities-panel.component';
|
||||||
export * from './lib/components/panel/activities-panel/broadcasting-activity/broadcasting-activity.component';
|
export * from './lib/components/panel/activities-panel/broadcasting-activity/broadcasting-activity.component';
|
||||||
|
|
@ -14,11 +15,10 @@ export * from './lib/components/panel/panel.component';
|
||||||
export * from './lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
|
export * from './lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component';
|
||||||
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
|
export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
|
||||||
export * from './lib/components/stream/stream.component';
|
export * from './lib/components/stream/stream.component';
|
||||||
export * from './lib/components/toolbar/toolbar.component';
|
|
||||||
export * from './lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component';
|
export * from './lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component';
|
||||||
export * from './lib/components/toolbar/toolbar-panel-buttons/toolbar-panel-buttons.component';
|
export * from './lib/components/toolbar/toolbar-panel-buttons/toolbar-panel-buttons.component';
|
||||||
|
export * from './lib/components/toolbar/toolbar.component';
|
||||||
export * from './lib/components/videoconference/videoconference.component';
|
export * from './lib/components/videoconference/videoconference.component';
|
||||||
export * from './lib/components/landscape-warning/landscape-warning.component';
|
|
||||||
export * from './lib/config/openvidu-components-angular.config';
|
export * from './lib/config/openvidu-components-angular.config';
|
||||||
// Directives
|
// Directives
|
||||||
export * from './lib/directives/api/activities-panel.directive';
|
export * from './lib/directives/api/activities-panel.directive';
|
||||||
|
|
@ -35,18 +35,19 @@ export * from './lib/directives/template/openvidu-components-angular.directive';
|
||||||
export * from './lib/directives/template/openvidu-components-angular.directive.module';
|
export * from './lib/directives/template/openvidu-components-angular.directive.module';
|
||||||
// Models
|
// Models
|
||||||
export * from './lib/models/broadcasting.model';
|
export * from './lib/models/broadcasting.model';
|
||||||
|
export * from './lib/models/data-topic.model';
|
||||||
|
export * from './lib/models/device.model';
|
||||||
|
export * from './lib/models/lang.model';
|
||||||
|
export * from './lib/models/layout.model';
|
||||||
|
export * from './lib/models/logger.model';
|
||||||
export * from './lib/models/panel.model';
|
export * from './lib/models/panel.model';
|
||||||
export * from './lib/models/participant.model';
|
export * from './lib/models/participant.model';
|
||||||
export * from './lib/models/recording.model';
|
export * from './lib/models/recording.model';
|
||||||
export * from './lib/models/data-topic.model';
|
|
||||||
export * from './lib/models/room.model';
|
export * from './lib/models/room.model';
|
||||||
export * from './lib/models/toolbar.model';
|
|
||||||
export * from './lib/models/logger.model';
|
|
||||||
export * from './lib/models/storage.model';
|
export * from './lib/models/storage.model';
|
||||||
export * from './lib/models/lang.model';
|
|
||||||
export * from './lib/models/theme.model';
|
export * from './lib/models/theme.model';
|
||||||
|
export * from './lib/models/toolbar.model';
|
||||||
export * from './lib/models/viewport.model';
|
export * from './lib/models/viewport.model';
|
||||||
export * from './lib/models/device.model';
|
|
||||||
// Pipes
|
// Pipes
|
||||||
export * from './lib/pipes/participant.pipe';
|
export * from './lib/pipes/participant.pipe';
|
||||||
export * from './lib/pipes/recording.pipe';
|
export * from './lib/pipes/recording.pipe';
|
||||||
|
|
@ -55,21 +56,21 @@ export * from './lib/pipes/translate.pipe';
|
||||||
export * from './lib/services/action/action.service';
|
export * from './lib/services/action/action.service';
|
||||||
export * from './lib/services/broadcasting/broadcasting.service';
|
export * from './lib/services/broadcasting/broadcasting.service';
|
||||||
export * from './lib/services/chat/chat.service';
|
export * from './lib/services/chat/chat.service';
|
||||||
|
export * from './lib/services/config/global-config.service';
|
||||||
|
export * from './lib/services/e2ee/e2ee.service';
|
||||||
export * from './lib/services/layout/layout.service';
|
export * from './lib/services/layout/layout.service';
|
||||||
|
export * from './lib/services/logger/logger.service';
|
||||||
export * from './lib/services/openvidu/openvidu.service';
|
export * from './lib/services/openvidu/openvidu.service';
|
||||||
export * from './lib/services/panel/panel.service';
|
export * from './lib/services/panel/panel.service';
|
||||||
export * from './lib/services/participant/participant.service';
|
export * from './lib/services/participant/participant.service';
|
||||||
export * from './lib/services/recording/recording.service';
|
export * from './lib/services/recording/recording.service';
|
||||||
export * from './lib/services/config/global-config.service';
|
|
||||||
export * from './lib/services/logger/logger.service';
|
|
||||||
export * from './lib/services/storage/storage.service';
|
export * from './lib/services/storage/storage.service';
|
||||||
export * from './lib/services/translate/translate.service';
|
|
||||||
export * from './lib/services/theme/theme.service';
|
export * from './lib/services/theme/theme.service';
|
||||||
|
export * from './lib/services/translate/translate.service';
|
||||||
export * from './lib/services/viewport/viewport.service';
|
export * from './lib/services/viewport/viewport.service';
|
||||||
export * from './lib/services/e2ee/e2ee.service';
|
|
||||||
//Modules
|
//Modules
|
||||||
export * from './lib/openvidu-components-angular.module';
|
|
||||||
export * from './lib/config/custom-cdk-overlay';
|
export * from './lib/config/custom-cdk-overlay';
|
||||||
export * from './lib/openvidu-components-angular-ui.module';
|
export * from './lib/openvidu-components-angular-ui.module';
|
||||||
|
export * from './lib/openvidu-components-angular.module';
|
||||||
|
|
||||||
export * from 'livekit-client';
|
export * from 'livekit-client';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue