Make screenshares appear near the presenter's tile and be larger
This commit is contained in:
		
					parent
					
						
							
								3e56d0a656
							
						
					
				
			
			
				commit
				
					
						391ba5196c
					
				
			
		
					 5 changed files with 166 additions and 51 deletions
				
			
		| 
						 | 
				
			
			@ -216,6 +216,7 @@ export function InCallView({
 | 
			
		|||
          focused: screenshareFeeds.length === 0 && callFeed === activeSpeaker,
 | 
			
		||||
          isLocal: member.userId === localUserId && deviceId === localDeviceId,
 | 
			
		||||
          presenter,
 | 
			
		||||
          largeBaseSize: false,
 | 
			
		||||
          connectionState,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -228,6 +229,7 @@ export function InCallView({
 | 
			
		|||
    // Add the screenshares too
 | 
			
		||||
    for (const screenshareFeed of screenshareFeeds) {
 | 
			
		||||
      const member = screenshareFeed.getMember()!;
 | 
			
		||||
      const deviceId = screenshareFeed.deviceId!;
 | 
			
		||||
      const connectionState = participants
 | 
			
		||||
        .get(member)
 | 
			
		||||
        ?.get(screenshareFeed.deviceId!)?.connectionState;
 | 
			
		||||
| 
						 | 
				
			
			@ -242,6 +244,8 @@ export function InCallView({
 | 
			
		|||
          focused: true,
 | 
			
		||||
          isLocal: screenshareFeed.isLocal(),
 | 
			
		||||
          presenter: false,
 | 
			
		||||
          largeBaseSize: true,
 | 
			
		||||
          placeNear: `${member.userId} ${deviceId}`,
 | 
			
		||||
          connectionState,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ import {
 | 
			
		|||
  fillGaps,
 | 
			
		||||
  forEachCellInArea,
 | 
			
		||||
  cycleTileSize,
 | 
			
		||||
  appendItems,
 | 
			
		||||
  addItems,
 | 
			
		||||
  tryMoveTile,
 | 
			
		||||
  resize,
 | 
			
		||||
} from "./model";
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ const useGridState = (
 | 
			
		|||
        grid2.cells.filter((c) => c !== undefined).map((c) => c!.item.id)
 | 
			
		||||
      );
 | 
			
		||||
      const newItems = items.filter((i) => !existingItemIds.has(i.id));
 | 
			
		||||
      const grid3 = appendItems(newItems, grid2);
 | 
			
		||||
      const grid3 = addItems(newItems, grid2);
 | 
			
		||||
 | 
			
		||||
      return { ...grid3, generation: prevGrid.generation + 1 };
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,5 +28,7 @@ export interface TileDescriptor {
 | 
			
		|||
  presenter: boolean;
 | 
			
		||||
  callFeed?: CallFeed;
 | 
			
		||||
  isLocal?: boolean;
 | 
			
		||||
  largeBaseSize: boolean;
 | 
			
		||||
  placeNear?: string;
 | 
			
		||||
  connectionState: ConnectionState;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -403,19 +403,94 @@ export function fillGaps(g: Grid): Grid {
 | 
			
		|||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function appendItems(items: TileDescriptor[], g: Grid): Grid {
 | 
			
		||||
  return {
 | 
			
		||||
    ...g,
 | 
			
		||||
    cells: [
 | 
			
		||||
      ...g.cells,
 | 
			
		||||
      ...items.map((i) => ({
 | 
			
		||||
        item: i,
 | 
			
		||||
        origin: true,
 | 
			
		||||
        columns: 1,
 | 
			
		||||
        rows: 1,
 | 
			
		||||
      })),
 | 
			
		||||
    ],
 | 
			
		||||
function createRows(g: Grid, count: number, atRow: number): Grid {
 | 
			
		||||
  const result = {
 | 
			
		||||
    columns: g.columns,
 | 
			
		||||
    cells: new Array(g.cells.length + g.columns * count),
 | 
			
		||||
  };
 | 
			
		||||
  const offsetAfterNewRows = g.columns * count;
 | 
			
		||||
 | 
			
		||||
  // Copy tiles from the original grid to the new one, with the new rows
 | 
			
		||||
  // inserted at the target location
 | 
			
		||||
  g.cells.forEach((c, from) => {
 | 
			
		||||
    if (c?.origin) {
 | 
			
		||||
      const offset = row(from, g) >= atRow ? offsetAfterNewRows : 0;
 | 
			
		||||
      forEachCellInArea(
 | 
			
		||||
        from,
 | 
			
		||||
        areaEnd(from, c.columns, c.rows, g),
 | 
			
		||||
        g,
 | 
			
		||||
        (c, i) => {
 | 
			
		||||
          result.cells[i + offset] = c;
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds a set of new items into the grid.
 | 
			
		||||
 */
 | 
			
		||||
export function addItems(items: TileDescriptor[], g: Grid): Grid {
 | 
			
		||||
  let result = cloneGrid(g);
 | 
			
		||||
 | 
			
		||||
  for (const item of items) {
 | 
			
		||||
    const cell = {
 | 
			
		||||
      item,
 | 
			
		||||
      origin: true,
 | 
			
		||||
      columns: 1,
 | 
			
		||||
      rows: 1,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let placeAt: number;
 | 
			
		||||
    let hasGaps: boolean;
 | 
			
		||||
 | 
			
		||||
    if (item.placeNear === undefined) {
 | 
			
		||||
      // This item has no special placement requests, so let's put it
 | 
			
		||||
      // uneventfully at the end of the grid
 | 
			
		||||
      placeAt = result.cells.length;
 | 
			
		||||
      hasGaps = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      // This item wants to be placed near another; let's put it on a row
 | 
			
		||||
      // directly below the related tile
 | 
			
		||||
      const placeNear = result.cells.findIndex(
 | 
			
		||||
        (c) => c?.item.id === item.placeNear
 | 
			
		||||
      );
 | 
			
		||||
      if (placeNear === -1) {
 | 
			
		||||
        // Can't find the related tile, so let's give up and place it at the end
 | 
			
		||||
        placeAt = result.cells.length;
 | 
			
		||||
        hasGaps = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        const placeNearCell = result.cells[placeNear]!;
 | 
			
		||||
        const placeNearEnd = areaEnd(
 | 
			
		||||
          placeNear,
 | 
			
		||||
          placeNearCell.columns,
 | 
			
		||||
          placeNearCell.rows,
 | 
			
		||||
          result
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        result = createRows(result, 1, row(placeNearEnd, result) + 1);
 | 
			
		||||
        placeAt =
 | 
			
		||||
          placeNear +
 | 
			
		||||
          Math.floor(placeNearCell.columns / 2) +
 | 
			
		||||
          result.columns * placeNearCell.rows;
 | 
			
		||||
        hasGaps = true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result.cells[placeAt] = cell;
 | 
			
		||||
 | 
			
		||||
    if (item.largeBaseSize) {
 | 
			
		||||
      // Cycle the tile size once to set up the tile with its larger base size
 | 
			
		||||
      // This also fills any gaps in the grid, hence no extra call to fillGaps
 | 
			
		||||
      result = cycleTileSize(item.id, result);
 | 
			
		||||
    } else if (hasGaps) {
 | 
			
		||||
      result = fillGaps(result);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const largeTileDimensions = (g: Grid): [number, number] => [
 | 
			
		||||
| 
						 | 
				
			
			@ -423,6 +498,9 @@ const largeTileDimensions = (g: Grid): [number, number] => [
 | 
			
		|||
  2,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const extraLargeTileDimensions = (g: Grid): [number, number] =>
 | 
			
		||||
  g.columns > 3 ? [4, 3] : [g.columns, 2];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Changes the size of a tile, rearranging the grid to make space.
 | 
			
		||||
 * @param tileId The ID of the tile to modify.
 | 
			
		||||
| 
						 | 
				
			
			@ -432,13 +510,19 @@ const largeTileDimensions = (g: Grid): [number, number] => [
 | 
			
		|||
export function cycleTileSize(tileId: string, g: Grid): Grid {
 | 
			
		||||
  const from = g.cells.findIndex((c) => c?.item.id === tileId);
 | 
			
		||||
  if (from === -1) return g; // Tile removed, no change
 | 
			
		||||
  const fromWidth = g.cells[from]!.columns;
 | 
			
		||||
  const fromHeight = g.cells[from]!.rows;
 | 
			
		||||
  const fromCell = g.cells[from]!;
 | 
			
		||||
  const fromWidth = fromCell.columns;
 | 
			
		||||
  const fromHeight = fromCell.rows;
 | 
			
		||||
  const fromEnd = areaEnd(from, fromWidth, fromHeight, g);
 | 
			
		||||
 | 
			
		||||
  // The target dimensions, which toggle between 1×1 and larger than 1×1
 | 
			
		||||
  const [baseDimensions, enlargedDimensions] = fromCell.item.largeBaseSize
 | 
			
		||||
    ? [largeTileDimensions(g), extraLargeTileDimensions(g)]
 | 
			
		||||
    : [[1, 1], largeTileDimensions(g)];
 | 
			
		||||
  // The target dimensions, which toggle between the base and enlarged sizes
 | 
			
		||||
  const [toWidth, toHeight] =
 | 
			
		||||
    fromWidth === 1 && fromHeight === 1 ? largeTileDimensions(g) : [1, 1];
 | 
			
		||||
    fromWidth === baseDimensions[0] && fromHeight === baseDimensions[1]
 | 
			
		||||
      ? enlargedDimensions
 | 
			
		||||
      : baseDimensions;
 | 
			
		||||
 | 
			
		||||
  // If we're expanding the tile, we want to create enough new rows at the
 | 
			
		||||
  // tile's target position such that every new unit of grid area created during
 | 
			
		||||
| 
						 | 
				
			
			@ -450,12 +534,6 @@ export function cycleTileSize(tileId: string, g: Grid): Grid {
 | 
			
		|||
    Math.ceil((toWidth * toHeight - fromWidth * fromHeight) / g.columns)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // This is the grid with the new rows added
 | 
			
		||||
  const gappyGrid: Grid = {
 | 
			
		||||
    ...g,
 | 
			
		||||
    cells: new Array(g.cells.length + newRows * g.columns),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // The next task is to scan for a spot to place the modified tile. Since we
 | 
			
		||||
  // might be creating new rows at the target position, this spot can be shorter
 | 
			
		||||
  // than the target height.
 | 
			
		||||
| 
						 | 
				
			
			@ -510,22 +588,19 @@ export function cycleTileSize(tileId: string, g: Grid): Grid {
 | 
			
		|||
 | 
			
		||||
  const toRow = row(to, g);
 | 
			
		||||
 | 
			
		||||
  // Copy tiles from the original grid to the new one, with the new rows
 | 
			
		||||
  // inserted at the target location
 | 
			
		||||
  g.cells.forEach((c, from) => {
 | 
			
		||||
    if (c?.origin && c.item.id !== tileId) {
 | 
			
		||||
      const offset =
 | 
			
		||||
        row(from, g) > toRow + candidateHeight - 1 ? g.columns * newRows : 0;
 | 
			
		||||
      forEachCellInArea(
 | 
			
		||||
        from,
 | 
			
		||||
        areaEnd(from, c.columns, c.rows, g),
 | 
			
		||||
        g,
 | 
			
		||||
        (c, i) => {
 | 
			
		||||
          gappyGrid.cells[i + offset] = c;
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  // This is the grid with the new rows added
 | 
			
		||||
  const gappyGrid = createRows(g, newRows, toRow + candidateHeight);
 | 
			
		||||
 | 
			
		||||
  // Remove the original tile
 | 
			
		||||
  const fromInGappyGrid =
 | 
			
		||||
    from + (row(from, g) >= toRow + candidateHeight ? g.columns * newRows : 0);
 | 
			
		||||
  const fromEndInGappyGrid = fromInGappyGrid - from + fromEnd;
 | 
			
		||||
  forEachCellInArea(
 | 
			
		||||
    fromInGappyGrid,
 | 
			
		||||
    fromEndInGappyGrid,
 | 
			
		||||
    gappyGrid,
 | 
			
		||||
    (_c, i) => (gappyGrid.cells[i] = undefined)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Place the tile in its target position, making a note of the tiles being
 | 
			
		||||
  // overwritten
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ limitations under the License.
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  appendItems,
 | 
			
		||||
  addItems,
 | 
			
		||||
  column,
 | 
			
		||||
  cycleTileSize,
 | 
			
		||||
  fillGaps,
 | 
			
		||||
| 
						 | 
				
			
			@ -313,20 +313,54 @@ dde
 | 
			
		|||
ddf`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
test("appendItems appends 1×1 tiles", () => {
 | 
			
		||||
  const grid1 = `
 | 
			
		||||
function testAddItems(
 | 
			
		||||
  title: string,
 | 
			
		||||
  items: TileDescriptor[],
 | 
			
		||||
  input: string,
 | 
			
		||||
  output: string
 | 
			
		||||
): void {
 | 
			
		||||
  test(`addItems ${title}`, () => {
 | 
			
		||||
    expect(showGrid(addItems(items, mkGrid(input)))).toBe(output);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
testAddItems(
 | 
			
		||||
  "appends 1×1 tiles",
 | 
			
		||||
  ["e", "f"].map((i) => ({ id: i } as unknown as TileDescriptor)),
 | 
			
		||||
  `
 | 
			
		||||
aab
 | 
			
		||||
aac
 | 
			
		||||
d`;
 | 
			
		||||
  const grid2 = `
 | 
			
		||||
d`,
 | 
			
		||||
  `
 | 
			
		||||
aab
 | 
			
		||||
aac
 | 
			
		||||
def`;
 | 
			
		||||
  const newItems = ["e", "f"].map(
 | 
			
		||||
    (i) => ({ id: i } as unknown as TileDescriptor)
 | 
			
		||||
  );
 | 
			
		||||
  expect(showGrid(appendItems(newItems, mkGrid(grid1)))).toBe(grid2);
 | 
			
		||||
});
 | 
			
		||||
def`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
testAddItems(
 | 
			
		||||
  "places one tile near another on request",
 | 
			
		||||
  [{ id: "g", placeNear: "b" } as unknown as TileDescriptor],
 | 
			
		||||
  `
 | 
			
		||||
abc
 | 
			
		||||
def`,
 | 
			
		||||
  `
 | 
			
		||||
abc
 | 
			
		||||
gfe
 | 
			
		||||
d`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
testAddItems(
 | 
			
		||||
  "places items with a large base size",
 | 
			
		||||
  [{ id: "g", largeBaseSize: true } as unknown as TileDescriptor],
 | 
			
		||||
  `
 | 
			
		||||
abc
 | 
			
		||||
def`,
 | 
			
		||||
  `
 | 
			
		||||
abc
 | 
			
		||||
ggf
 | 
			
		||||
gge
 | 
			
		||||
d`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function testTryMoveTile(
 | 
			
		||||
  title: string,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue