Implement the new one-on-one layout
This commit is contained in:
@@ -19,22 +19,36 @@ import { ComponentType } from "react";
|
|||||||
|
|
||||||
import { MediaViewModel } from "../state/MediaViewModel";
|
import { MediaViewModel } from "../state/MediaViewModel";
|
||||||
import { LayoutProps } from "./Grid";
|
import { LayoutProps } from "./Grid";
|
||||||
import { Alignment } from "../room/InCallView";
|
|
||||||
|
|
||||||
export interface Bounds {
|
export interface Bounds {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Alignment {
|
||||||
|
inline: "start" | "end";
|
||||||
|
block: "start" | "end";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultSpotlightAlignment: Alignment = {
|
||||||
|
inline: "end",
|
||||||
|
block: "end",
|
||||||
|
};
|
||||||
|
export const defaultPipAlignment: Alignment = { inline: "end", block: "start" };
|
||||||
|
|
||||||
export interface CallLayoutInputs {
|
export interface CallLayoutInputs {
|
||||||
/**
|
/**
|
||||||
* The minimum bounds of the layout area.
|
* The minimum bounds of the layout area.
|
||||||
*/
|
*/
|
||||||
minBounds: Observable<Bounds>;
|
minBounds: Observable<Bounds>;
|
||||||
/**
|
/**
|
||||||
* The alignment of the floating tile, if any.
|
* The alignment of the floating spotlight tile, if present.
|
||||||
*/
|
*/
|
||||||
floatingAlignment: BehaviorSubject<Alignment>;
|
spotlightAlignment: BehaviorSubject<Alignment>;
|
||||||
|
/**
|
||||||
|
* The alignment of the small picture-in-picture tile, if present.
|
||||||
|
*/
|
||||||
|
pipAlignment: BehaviorSubject<Alignment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GridTileModel {
|
export interface GridTileModel {
|
||||||
@@ -67,3 +81,68 @@ export interface CallLayoutOutputs<Model> {
|
|||||||
export type CallLayout<Model> = (
|
export type CallLayout<Model> = (
|
||||||
inputs: CallLayoutInputs,
|
inputs: CallLayoutInputs,
|
||||||
) => CallLayoutOutputs<Model>;
|
) => CallLayoutOutputs<Model>;
|
||||||
|
|
||||||
|
export interface GridArrangement {
|
||||||
|
tileWidth: number;
|
||||||
|
tileHeight: number;
|
||||||
|
gap: number;
|
||||||
|
columns: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileMinHeight = 130;
|
||||||
|
const tileMaxAspectRatio = 17 / 9;
|
||||||
|
const tileMinAspectRatio = 4 / 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the ideal arrangement of tiles into a grid of a particular size.
|
||||||
|
*/
|
||||||
|
export function arrangeTiles(
|
||||||
|
width: number,
|
||||||
|
minHeight: number,
|
||||||
|
tileCount: number,
|
||||||
|
): GridArrangement {
|
||||||
|
// The goal here is to determine the grid size and padding that maximizes
|
||||||
|
// use of screen space for n tiles without making those tiles too small or
|
||||||
|
// too cropped (having an extreme aspect ratio)
|
||||||
|
const gap = width < 800 ? 16 : 20;
|
||||||
|
const tileMinWidth = width < 500 ? 150 : 180;
|
||||||
|
|
||||||
|
let columns = Math.min(
|
||||||
|
// Don't create more columns than we have items for
|
||||||
|
tileCount,
|
||||||
|
// The ideal number of columns is given by a packing of equally-sized
|
||||||
|
// squares into a grid.
|
||||||
|
// width / column = height / row.
|
||||||
|
// columns * rows = number of squares.
|
||||||
|
// ∴ columns = sqrt(width / height * number of squares).
|
||||||
|
// Except we actually want 16:9-ish tiles rather than squares, so we
|
||||||
|
// divide the width-to-height ratio by the target aspect ratio.
|
||||||
|
Math.ceil(Math.sqrt((width / minHeight / tileMaxAspectRatio) * tileCount)),
|
||||||
|
);
|
||||||
|
let rows = Math.ceil(tileCount / columns);
|
||||||
|
|
||||||
|
let tileWidth = (width - (columns - 1) * gap) / columns;
|
||||||
|
let tileHeight = (minHeight - (rows - 1) * gap) / rows;
|
||||||
|
|
||||||
|
// Impose a minimum width and height on the tiles
|
||||||
|
if (tileWidth < tileMinWidth) {
|
||||||
|
// In this case we want the tile width to determine the number of columns,
|
||||||
|
// not the other way around. If we take the above equation for the tile
|
||||||
|
// width (w = (W - (c - 1) * g) / c) and solve for c, we get
|
||||||
|
// c = (W + g) / (w + g).
|
||||||
|
columns = Math.floor((width + gap) / (tileMinWidth + gap));
|
||||||
|
rows = Math.ceil(tileCount / columns);
|
||||||
|
tileWidth = (width - (columns - 1) * gap) / columns;
|
||||||
|
tileHeight = (minHeight - (rows - 1) * gap) / rows;
|
||||||
|
}
|
||||||
|
if (tileHeight < tileMinHeight) tileHeight = tileMinHeight;
|
||||||
|
// Impose a minimum and maximum aspect ratio on the tiles
|
||||||
|
const tileAspectRatio = tileWidth / tileHeight;
|
||||||
|
if (tileAspectRatio > tileMaxAspectRatio)
|
||||||
|
tileWidth = tileHeight * tileMaxAspectRatio;
|
||||||
|
else if (tileAspectRatio < tileMinAspectRatio)
|
||||||
|
tileHeight = tileWidth / tileMinAspectRatio;
|
||||||
|
// TODO: We might now be hitting the minimum height or width limit again
|
||||||
|
|
||||||
|
return { tileWidth, tileHeight, gap, columns };
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ limitations under the License.
|
|||||||
.fixed,
|
.fixed,
|
||||||
.scrolling {
|
.scrolling {
|
||||||
margin-inline: var(--inline-content-inset);
|
margin-inline: var(--inline-content-inset);
|
||||||
|
block-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrolling {
|
.scrolling {
|
||||||
box-sizing: border-box;
|
|
||||||
block-size: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ import { GridLayout as GridLayoutModel } from "../state/CallViewModel";
|
|||||||
import styles from "./GridLayout.module.css";
|
import styles from "./GridLayout.module.css";
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
import { useInitial } from "../useInitial";
|
import { useInitial } from "../useInitial";
|
||||||
import { CallLayout, GridTileModel, TileModel } from "./CallLayout";
|
import {
|
||||||
|
CallLayout,
|
||||||
|
GridTileModel,
|
||||||
|
TileModel,
|
||||||
|
arrangeTiles,
|
||||||
|
} from "./CallLayout";
|
||||||
import { DragCallback } from "./Grid";
|
import { DragCallback } from "./Grid";
|
||||||
|
|
||||||
interface GridCSSProperties extends CSSProperties {
|
interface GridCSSProperties extends CSSProperties {
|
||||||
@@ -31,13 +36,9 @@ interface GridCSSProperties extends CSSProperties {
|
|||||||
"--height": string;
|
"--height": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotMinHeight = 130;
|
|
||||||
const slotMaxAspectRatio = 17 / 9;
|
|
||||||
const slotMinAspectRatio = 4 / 3;
|
|
||||||
|
|
||||||
export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
||||||
minBounds,
|
minBounds,
|
||||||
floatingAlignment,
|
spotlightAlignment,
|
||||||
}) => ({
|
}) => ({
|
||||||
// The "fixed" (non-scrolling) part of the layout is where the spotlight tile
|
// The "fixed" (non-scrolling) part of the layout is where the spotlight tile
|
||||||
// lives
|
// lives
|
||||||
@@ -45,7 +46,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
const { width, height } = useObservableEagerState(minBounds);
|
const { width, height } = useObservableEagerState(minBounds);
|
||||||
const alignment = useObservableEagerState(
|
const alignment = useObservableEagerState(
|
||||||
useInitial(() =>
|
useInitial(() =>
|
||||||
floatingAlignment.pipe(
|
spotlightAlignment.pipe(
|
||||||
distinctUntilChanged(
|
distinctUntilChanged(
|
||||||
(a1, a2) => a1.block === a2.block && a1.inline === a2.inline,
|
(a1, a2) => a1.block === a2.block && a1.inline === a2.inline,
|
||||||
),
|
),
|
||||||
@@ -68,7 +69,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
|
|
||||||
const onDragSpotlight: DragCallback = useCallback(
|
const onDragSpotlight: DragCallback = useCallback(
|
||||||
({ xRatio, yRatio }) =>
|
({ xRatio, yRatio }) =>
|
||||||
floatingAlignment.next({
|
spotlightAlignment.next({
|
||||||
block: yRatio < 0.5 ? "start" : "end",
|
block: yRatio < 0.5 ? "start" : "end",
|
||||||
inline: xRatio < 0.5 ? "start" : "end",
|
inline: xRatio < 0.5 ? "start" : "end",
|
||||||
}),
|
}),
|
||||||
@@ -76,12 +77,7 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={ref} className={styles.fixed} data-generation={generation}>
|
||||||
ref={ref}
|
|
||||||
className={styles.fixed}
|
|
||||||
data-generation={generation}
|
|
||||||
style={{ height }}
|
|
||||||
>
|
|
||||||
{tileModel && (
|
{tileModel && (
|
||||||
<Slot
|
<Slot
|
||||||
className={styles.slot}
|
className={styles.slot}
|
||||||
@@ -99,57 +95,10 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
// The scrolling part of the layout is where all the grid tiles live
|
// The scrolling part of the layout is where all the grid tiles live
|
||||||
scrolling: forwardRef(function GridLayout({ model, Slot }, ref) {
|
scrolling: forwardRef(function GridLayout({ model, Slot }, ref) {
|
||||||
const { width, height: minHeight } = useObservableEagerState(minBounds);
|
const { width, height: minHeight } = useObservableEagerState(minBounds);
|
||||||
|
const { gap, tileWidth, tileHeight } = useMemo(
|
||||||
// The goal here is to determine the grid size and padding that maximizes
|
() => arrangeTiles(width, minHeight, model.grid.length),
|
||||||
// use of screen space for n tiles without making those tiles too small or
|
[width, minHeight, model.grid.length],
|
||||||
// too cropped (having an extreme aspect ratio)
|
|
||||||
const [gap, slotWidth, slotHeight] = useMemo(() => {
|
|
||||||
const gap = width < 800 ? 16 : 20;
|
|
||||||
const slotMinWidth = width < 500 ? 150 : 180;
|
|
||||||
|
|
||||||
let columns = Math.min(
|
|
||||||
// Don't create more columns than we have items for
|
|
||||||
model.grid.length,
|
|
||||||
// The ideal number of columns is given by a packing of equally-sized
|
|
||||||
// squares into a grid.
|
|
||||||
// width / column = height / row.
|
|
||||||
// columns * rows = number of squares.
|
|
||||||
// ∴ columns = sqrt(width / height * number of squares).
|
|
||||||
// Except we actually want 16:9-ish slots rather than squares, so we
|
|
||||||
// divide the width-to-height ratio by the target aspect ratio.
|
|
||||||
Math.ceil(
|
|
||||||
Math.sqrt(
|
|
||||||
(width / minHeight / slotMaxAspectRatio) * model.grid.length,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
let rows = Math.ceil(model.grid.length / columns);
|
|
||||||
|
|
||||||
let slotWidth = (width - (columns - 1) * gap) / columns;
|
|
||||||
let slotHeight = (minHeight - (rows - 1) * gap) / rows;
|
|
||||||
|
|
||||||
// Impose a minimum width and height on the slots
|
|
||||||
if (slotWidth < slotMinWidth) {
|
|
||||||
// In this case we want the slot width to determine the number of columns,
|
|
||||||
// not the other way around. If we take the above equation for the slot
|
|
||||||
// width (w = (W - (c - 1) * g) / c) and solve for c, we get
|
|
||||||
// c = (W + g) / (w + g).
|
|
||||||
columns = Math.floor((width + gap) / (slotMinWidth + gap));
|
|
||||||
rows = Math.ceil(model.grid.length / columns);
|
|
||||||
slotWidth = (width - (columns - 1) * gap) / columns;
|
|
||||||
slotHeight = (minHeight - (rows - 1) * gap) / rows;
|
|
||||||
}
|
|
||||||
if (slotHeight < slotMinHeight) slotHeight = slotMinHeight;
|
|
||||||
// Impose a minimum and maximum aspect ratio on the slots
|
|
||||||
const slotAspectRatio = slotWidth / slotHeight;
|
|
||||||
if (slotAspectRatio > slotMaxAspectRatio)
|
|
||||||
slotWidth = slotHeight * slotMaxAspectRatio;
|
|
||||||
else if (slotAspectRatio < slotMinAspectRatio)
|
|
||||||
slotHeight = slotWidth / slotMinAspectRatio;
|
|
||||||
// TODO: We might now be hitting the minimum height or width limit again
|
|
||||||
|
|
||||||
return [gap, slotWidth, slotHeight];
|
|
||||||
}, [width, minHeight, model.grid.length]);
|
|
||||||
|
|
||||||
const [generation] = useReactiveState<number>(
|
const [generation] = useReactiveState<number>(
|
||||||
(prev) => (prev === undefined ? 0 : prev + 1),
|
(prev) => (prev === undefined ? 0 : prev + 1),
|
||||||
@@ -170,8 +119,8 @@ export const makeGridLayout: CallLayout<GridLayoutModel> = ({
|
|||||||
{
|
{
|
||||||
width,
|
width,
|
||||||
"--gap": `${gap}px`,
|
"--gap": `${gap}px`,
|
||||||
"--width": `${Math.floor(slotWidth)}px`,
|
"--width": `${Math.floor(tileWidth)}px`,
|
||||||
"--height": `${Math.floor(slotHeight)}px`,
|
"--height": `${Math.floor(tileHeight)}px`,
|
||||||
} as GridCSSProperties
|
} as GridCSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
56
src/grid/OneOnOneLayout.module.css
Normal file
56
src/grid/OneOnOneLayout.module.css
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.layer {
|
||||||
|
margin-inline: var(--inline-content-inset);
|
||||||
|
block-size: 100%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.local {
|
||||||
|
position: absolute;
|
||||||
|
inline-size: 180px;
|
||||||
|
block-size: 135px;
|
||||||
|
inset: var(--cpd-space-4x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotlight {
|
||||||
|
position: absolute;
|
||||||
|
inline-size: 404px;
|
||||||
|
block-size: 233px;
|
||||||
|
inset: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot[data-block-alignment="start"] {
|
||||||
|
inset-block-end: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot[data-block-alignment="end"] {
|
||||||
|
inset-block-start: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot[data-inline-alignment="start"] {
|
||||||
|
inset-inline-end: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slot[data-inline-alignment="end"] {
|
||||||
|
inset-inline-start: unset;
|
||||||
|
}
|
||||||
132
src/grid/OneOnOneLayout.tsx
Normal file
132
src/grid/OneOnOneLayout.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { forwardRef, useCallback, useMemo } from "react";
|
||||||
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
import { OneOnOneLayout as OneOnOneLayoutModel } from "../state/CallViewModel";
|
||||||
|
import {
|
||||||
|
CallLayout,
|
||||||
|
GridTileModel,
|
||||||
|
SpotlightTileModel,
|
||||||
|
arrangeTiles,
|
||||||
|
} from "./CallLayout";
|
||||||
|
import { useReactiveState } from "../useReactiveState";
|
||||||
|
import styles from "./OneOnOneLayout.module.css";
|
||||||
|
import { DragCallback } from "./Grid";
|
||||||
|
|
||||||
|
export const makeOneOnOneLayout: CallLayout<OneOnOneLayoutModel> = ({
|
||||||
|
minBounds,
|
||||||
|
spotlightAlignment,
|
||||||
|
pipAlignment,
|
||||||
|
}) => ({
|
||||||
|
fixed: forwardRef(function OneOnOneLayoutFixed({ model, Slot }, ref) {
|
||||||
|
const { width, height } = useObservableEagerState(minBounds);
|
||||||
|
const spotlightAlignmentValue = useObservableEagerState(spotlightAlignment);
|
||||||
|
|
||||||
|
const [generation] = useReactiveState<number>(
|
||||||
|
(prev) => (prev === undefined ? 0 : prev + 1),
|
||||||
|
[width, height, model.spotlight === undefined, spotlightAlignmentValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const spotlightTileModel: SpotlightTileModel | undefined = useMemo(
|
||||||
|
() =>
|
||||||
|
model.spotlight && {
|
||||||
|
type: "spotlight",
|
||||||
|
vms: model.spotlight,
|
||||||
|
maximised: false,
|
||||||
|
},
|
||||||
|
[model.spotlight],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragSpotlight: DragCallback = useCallback(
|
||||||
|
({ xRatio, yRatio }) =>
|
||||||
|
spotlightAlignment.next({
|
||||||
|
block: yRatio < 0.5 ? "start" : "end",
|
||||||
|
inline: xRatio < 0.5 ? "start" : "end",
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} data-generation={generation} className={styles.layer}>
|
||||||
|
{spotlightTileModel && (
|
||||||
|
<Slot
|
||||||
|
className={classNames(styles.slot, styles.spotlight)}
|
||||||
|
id="spotlight"
|
||||||
|
model={spotlightTileModel}
|
||||||
|
onDrag={onDragSpotlight}
|
||||||
|
data-block-alignment={spotlightAlignmentValue.block}
|
||||||
|
data-inline-alignment={spotlightAlignmentValue.inline}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
scrolling: forwardRef(function OneOnOneLayoutScrolling({ model, Slot }, ref) {
|
||||||
|
const { width, height } = useObservableEagerState(minBounds);
|
||||||
|
const pipAlignmentValue = useObservableEagerState(pipAlignment);
|
||||||
|
const { tileWidth, tileHeight } = useMemo(
|
||||||
|
() => arrangeTiles(width, height, 1),
|
||||||
|
[width, height],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [generation] = useReactiveState<number>(
|
||||||
|
(prev) => (prev === undefined ? 0 : prev + 1),
|
||||||
|
[width, height, pipAlignmentValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const remoteTileModel: GridTileModel = useMemo(
|
||||||
|
() => ({ type: "grid", vm: model.remote }),
|
||||||
|
[model.remote],
|
||||||
|
);
|
||||||
|
const localTileModel: GridTileModel = useMemo(
|
||||||
|
() => ({ type: "grid", vm: model.local }),
|
||||||
|
[model.local],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDragLocalTile: DragCallback = useCallback(
|
||||||
|
({ xRatio, yRatio }) =>
|
||||||
|
pipAlignment.next({
|
||||||
|
block: yRatio < 0.5 ? "start" : "end",
|
||||||
|
inline: xRatio < 0.5 ? "start" : "end",
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} data-generation={generation} className={styles.layer}>
|
||||||
|
<Slot
|
||||||
|
id={remoteTileModel.vm.id}
|
||||||
|
model={remoteTileModel}
|
||||||
|
className={styles.container}
|
||||||
|
style={{ width: tileWidth, height: tileHeight }}
|
||||||
|
>
|
||||||
|
<Slot
|
||||||
|
className={classNames(styles.slot, styles.local)}
|
||||||
|
id={localTileModel.vm.id}
|
||||||
|
model={localTileModel}
|
||||||
|
onDrag={onDragLocalTile}
|
||||||
|
data-block-alignment={pipAlignmentValue.block}
|
||||||
|
data-inline-alignment={pipAlignmentValue.inline}
|
||||||
|
/>
|
||||||
|
</Slot>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
.layer {
|
.layer {
|
||||||
margin-inline: var(--inline-content-inset);
|
margin-inline: var(--inline-content-inset);
|
||||||
|
block-size: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
--grid-gap: 20px;
|
--grid-gap: 20px;
|
||||||
gap: 30px;
|
gap: 30px;
|
||||||
@@ -30,10 +31,6 @@ limitations under the License.
|
|||||||
grid-template-rows: minmax(1fr, auto);
|
grid-template-rows: minmax(1fr, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrolling {
|
|
||||||
block-size: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spotlight {
|
.spotlight {
|
||||||
container: spotlight / size;
|
container: spotlight / size;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -69,10 +69,8 @@ export const makeSpotlightLayout: CallLayout<SpotlightLayoutModel> = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
data-generation={generation}
|
data-generation={generation}
|
||||||
data-orientation={layout.orientation}
|
data-orientation={layout.orientation}
|
||||||
className={classNames(styles.layer, styles.fixed)}
|
className={styles.layer}
|
||||||
style={
|
style={{ "--grid-columns": layout.gridColumns } as GridCSSProperties}
|
||||||
{ "--grid-columns": layout.gridColumns, height } as GridCSSProperties
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div className={styles.spotlight}>
|
<div className={styles.spotlight}>
|
||||||
<Slot className={styles.slot} id="spotlight" model={tileModel} />
|
<Slot className={styles.slot} id="spotlight" model={tileModel} />
|
||||||
@@ -102,7 +100,7 @@ export const makeSpotlightLayout: CallLayout<SpotlightLayoutModel> = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
data-generation={generation}
|
data-generation={generation}
|
||||||
data-orientation={layout.orientation}
|
data-orientation={layout.orientation}
|
||||||
className={classNames(styles.layer, styles.scrolling)}
|
className={styles.layer}
|
||||||
style={{ "--grid-columns": layout.gridColumns } as GridCSSProperties}
|
style={{ "--grid-columns": layout.gridColumns } as GridCSSProperties}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -87,17 +87,17 @@ import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
|
|||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { makeGridLayout } from "../grid/GridLayout";
|
import { makeGridLayout } from "../grid/GridLayout";
|
||||||
import { makeSpotlightLayout } from "../grid/SpotlightLayout";
|
import { makeSpotlightLayout } from "../grid/SpotlightLayout";
|
||||||
import { CallLayout, GridTileModel, TileModel } from "../grid/CallLayout";
|
import {
|
||||||
|
CallLayout,
|
||||||
|
GridTileModel,
|
||||||
|
TileModel,
|
||||||
|
defaultPipAlignment,
|
||||||
|
defaultSpotlightAlignment,
|
||||||
|
} from "../grid/CallLayout";
|
||||||
|
import { makeOneOnOneLayout } from "../grid/OneOnOneLayout";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
|
|
||||||
export interface Alignment {
|
|
||||||
inline: "start" | "end";
|
|
||||||
block: "start" | "end";
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultAlignment: Alignment = { inline: "end", block: "end" };
|
|
||||||
|
|
||||||
const dummySpotlightItem = {
|
const dummySpotlightItem = {
|
||||||
id: "spotlight",
|
id: "spotlight",
|
||||||
} as TileDescriptor<MediaViewModel>;
|
} as TileDescriptor<MediaViewModel>;
|
||||||
@@ -321,8 +321,11 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
);
|
);
|
||||||
const gridBoundsObservable = useObservable(gridBounds);
|
const gridBoundsObservable = useObservable(gridBounds);
|
||||||
|
|
||||||
const floatingAlignment = useInitial(
|
const spotlightAlignment = useInitial(
|
||||||
() => new BehaviorSubject(defaultAlignment),
|
() => new BehaviorSubject(defaultSpotlightAlignment),
|
||||||
|
);
|
||||||
|
const pipAlignment = useInitial(
|
||||||
|
() => new BehaviorSubject(defaultPipAlignment),
|
||||||
);
|
);
|
||||||
|
|
||||||
const layoutSystem = useObservableEagerState(
|
const layoutSystem = useObservableEagerState(
|
||||||
@@ -334,11 +337,14 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
makeLayout = makeGridLayout as CallLayout<Layout>;
|
makeLayout = makeGridLayout as CallLayout<Layout>;
|
||||||
else if (l.type === "spotlight")
|
else if (l.type === "spotlight")
|
||||||
makeLayout = makeSpotlightLayout as CallLayout<Layout>;
|
makeLayout = makeSpotlightLayout as CallLayout<Layout>;
|
||||||
|
else if (l.type === "one-on-one")
|
||||||
|
makeLayout = makeOneOnOneLayout as CallLayout<Layout>;
|
||||||
else return null; // Not yet implemented
|
else return null; // Not yet implemented
|
||||||
|
|
||||||
return makeLayout({
|
return makeLayout({
|
||||||
minBounds: gridBoundsObservable,
|
minBounds: gridBoundsObservable,
|
||||||
floatingAlignment,
|
spotlightAlignment,
|
||||||
|
pipAlignment,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -491,7 +497,10 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
/>
|
/>
|
||||||
<Grid
|
<Grid
|
||||||
className={styles.fixedGrid}
|
className={styles.fixedGrid}
|
||||||
style={{ insetBlockStart: headerBounds.bottom }}
|
style={{
|
||||||
|
insetBlockStart: headerBounds.bottom,
|
||||||
|
height: gridBounds.height,
|
||||||
|
}}
|
||||||
model={layout}
|
model={layout}
|
||||||
Layout={layoutSystem.fixed}
|
Layout={layoutSystem.fixed}
|
||||||
Tile={Tile}
|
Tile={Tile}
|
||||||
|
|||||||
Reference in New Issue
Block a user