feat: Add deafen to pc, fix mobiel view, fix freeze on startup

This commit is contained in:
2026-06-05 15:27:06 +02:00
parent 35f52b0356
commit a675f12e61
85 changed files with 2499 additions and 519 deletions

View File

@@ -0,0 +1,67 @@
import {
describe,
expect,
it
} from 'vitest';
import { buildChatMessageImageGridLayout, formatChatMessageImageOverflowLabel } from './chat-message-image-grid.rules';
describe('chat-message-image-grid rules', () => {
it('keeps a single image outside the grid', () => {
expect(buildChatMessageImageGridLayout(1)).toEqual({
useGrid: false,
variant: 'none',
cells: []
});
});
it('lays out two images in a pair grid', () => {
expect(buildChatMessageImageGridLayout(2)).toEqual({
useGrid: true,
variant: 'pair',
cells: [{ kind: 'image', index: 0 }, { kind: 'image', index: 1 }]
});
});
it('lays out three images in a triple grid', () => {
expect(buildChatMessageImageGridLayout(3)).toEqual({
useGrid: true,
variant: 'triple',
cells: [
{ kind: 'image', index: 0 },
{ kind: 'image', index: 1 },
{ kind: 'image', index: 2 }
]
});
});
it('lays out four images in a quad grid', () => {
expect(buildChatMessageImageGridLayout(4)).toEqual({
useGrid: true,
variant: 'quad',
cells: [
{ kind: 'image', index: 0 },
{ kind: 'image', index: 1 },
{ kind: 'image', index: 2 },
{ kind: 'image', index: 3 }
]
});
});
it('replaces the last grid cell with an overflow tile when more than four images exist', () => {
expect(buildChatMessageImageGridLayout(7)).toEqual({
useGrid: true,
variant: 'quad',
cells: [
{ kind: 'image', index: 0 },
{ kind: 'image', index: 1 },
{ kind: 'image', index: 2 },
{ kind: 'overflow', hiddenCount: 4 }
]
});
});
it('formats overflow labels as amount plus', () => {
expect(formatChatMessageImageOverflowLabel(4)).toBe('+4');
});
});

View File

@@ -0,0 +1,83 @@
export const CHAT_MESSAGE_IMAGE_GRID_MIN_COUNT = 2;
export const CHAT_MESSAGE_IMAGE_GRID_MAX_VISIBLE = 4;
export type ChatMessageImageGridVariant = 'none' | 'pair' | 'triple' | 'quad';
export interface ChatMessageImageGridImageCell {
kind: 'image';
index: number;
}
export interface ChatMessageImageGridOverflowCell {
kind: 'overflow';
hiddenCount: number;
}
export type ChatMessageImageGridCell = ChatMessageImageGridImageCell | ChatMessageImageGridOverflowCell;
export interface ChatMessageImageGridLayout {
useGrid: boolean;
variant: ChatMessageImageGridVariant;
cells: ChatMessageImageGridCell[];
}
export function buildChatMessageImageGridLayout(imageCount: number): ChatMessageImageGridLayout {
if (imageCount < CHAT_MESSAGE_IMAGE_GRID_MIN_COUNT) {
return {
useGrid: false,
variant: 'none',
cells: []
};
}
if (imageCount === 2) {
return {
useGrid: true,
variant: 'pair',
cells: [{ kind: 'image', index: 0 }, { kind: 'image', index: 1 }]
};
}
if (imageCount === 3) {
return {
useGrid: true,
variant: 'triple',
cells: [
{ kind: 'image', index: 0 },
{ kind: 'image', index: 1 },
{ kind: 'image', index: 2 }
]
};
}
if (imageCount === CHAT_MESSAGE_IMAGE_GRID_MAX_VISIBLE) {
return {
useGrid: true,
variant: 'quad',
cells: [
{ kind: 'image', index: 0 },
{ kind: 'image', index: 1 },
{ kind: 'image', index: 2 },
{ kind: 'image', index: 3 }
]
};
}
return {
useGrid: true,
variant: 'quad',
cells: [
{ kind: 'image', index: 0 },
{ kind: 'image', index: 1 },
{ kind: 'image', index: 2 },
{
kind: 'overflow',
hiddenCount: imageCount - 3
}
]
};
}
export function formatChatMessageImageOverflowLabel(hiddenCount: number): string {
return `+${hiddenCount}`;
}

View File

@@ -0,0 +1,29 @@
import {
describe,
expect,
it
} from 'vitest';
import { canStepLightbox, stepLightboxIndex } from './chat-message-lightbox.rules';
describe('chat-message-lightbox rules', () => {
it('steps forward and backward within bounds', () => {
expect(stepLightboxIndex(1, 1, 3)).toBe(2);
expect(stepLightboxIndex(1, -1, 3)).toBe(0);
});
it('returns null when stepping past the ends', () => {
expect(stepLightboxIndex(0, -1, 3)).toBeNull();
expect(stepLightboxIndex(2, 1, 3)).toBeNull();
});
it('returns null when there is only one image', () => {
expect(stepLightboxIndex(0, 1, 1)).toBeNull();
});
it('reports whether a step is available', () => {
expect(canStepLightbox(0, -1, 3)).toBe(false);
expect(canStepLightbox(0, 1, 3)).toBe(true);
expect(canStepLightbox(2, 1, 3)).toBe(false);
});
});

View File

@@ -0,0 +1,17 @@
export function stepLightboxIndex(currentIndex: number, delta: number, total: number): number | null {
if (total <= 1) {
return null;
}
const nextIndex = currentIndex + delta;
if (nextIndex < 0 || nextIndex >= total) {
return null;
}
return nextIndex;
}
export function canStepLightbox(currentIndex: number, delta: number, total: number): boolean {
return stepLightboxIndex(currentIndex, delta, total) !== null;
}