a9505ca5b2
Soundboard v2 — a near-parallel of the custom-emoji image-pack system for in-call audio clips. - Data model: 3-tier packs mirroring MSC2545 — room/space pack (state event io.lotus.soundboard, inherited by child rooms via parent-space aggregation), global refs (io.lotus.soundboard_rooms), and the personal pack (io.lotus.soundboard account data; the v1 flat-list content is migrated to the pack shape on read). New plugins/soundboard/ (readers, SoundboardPack, utils) + hooks/useSoundboardPacks (useRelevantSoundboardPacks = user U global U room, deduped). Unit-tested (migration + slug). - Management: reusable SoundboardPackEditor (name + emoji + per-clip volume + delete + upload + batched save), power-level-gated for room packs like emoji packs; a Soundboard page wired into Room + Space settings. - In-call: CallSoundboard rewritten as a Discord-style grid grouped by pack (emoji + name tiles), sourcing room+parent-space U personal clips; a Manage toggle embeds the editors; per-clip volume x master volume on playback. - Spam guard: host gates on a playing key (fork enforces one clip at a time). - Control bar: Mute-Screenshare moved next to the Screenshare button. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import { MatrixEvent } from 'matrix-js-sdk';
|
|
import { PackAddress } from '../custom-emoji/PackAddress';
|
|
import { SoundboardClipReader } from './SoundboardClipReader';
|
|
import { SoundboardClipsReader } from './SoundboardClipsReader';
|
|
import { SoundboardMetaReader } from './SoundboardMetaReader';
|
|
import { SoundboardContent } from './types';
|
|
|
|
/** Parallels custom-emoji/ImagePack. Holds a soundboard pack's meta + clips. */
|
|
export class SoundboardPack {
|
|
public readonly id: string;
|
|
|
|
public readonly deleted: boolean;
|
|
|
|
public readonly address: PackAddress | undefined;
|
|
|
|
public readonly meta: SoundboardMetaReader;
|
|
|
|
public readonly clips: SoundboardClipsReader;
|
|
|
|
private clipsMemo: SoundboardClipReader[] | undefined;
|
|
|
|
constructor(id: string, content: SoundboardContent, address: PackAddress | undefined) {
|
|
this.id = id;
|
|
this.address = address;
|
|
this.deleted = content.pack === undefined && content.clips === undefined;
|
|
this.meta = new SoundboardMetaReader(content.pack ?? {});
|
|
this.clips = new SoundboardClipsReader(content.clips ?? {});
|
|
}
|
|
|
|
static fromMatrixEvent(id: string, matrixEvent: MatrixEvent): SoundboardPack {
|
|
const roomId = matrixEvent.getRoomId();
|
|
const stateKey = matrixEvent.getStateKey();
|
|
const address =
|
|
roomId && typeof stateKey === 'string' ? new PackAddress(roomId, stateKey) : undefined;
|
|
return new SoundboardPack(id, matrixEvent.getContent<SoundboardContent>(), address);
|
|
}
|
|
|
|
getClips(): SoundboardClipReader[] {
|
|
if (this.clipsMemo) return this.clipsMemo;
|
|
this.clipsMemo = Array.from(this.clips.collection.values());
|
|
return this.clipsMemo;
|
|
}
|
|
|
|
getAvatarUrl(): string | undefined {
|
|
if (this.meta.avatar) return this.meta.avatar;
|
|
return undefined;
|
|
}
|
|
}
|