Text channels between mumh5 users; drag channels in front of or behind another

Text channels are real Mumble channels with a marker in the description.
Their messages travel as plugin data to everyone on the server, are kept
by the clients and handed to people who connect later.

Channels can be dropped in front of or behind another channel, with a
line where they land. A description preview no longer sticks after a drop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 20:16:03 +02:00
co-authored by Claude Opus 5.5
parent 06a185d778
commit 30a75a0629
18 changed files with 892 additions and 54 deletions
+25
View File
@@ -0,0 +1,25 @@
// Where a channel sits among its siblings. Mumble sorts by position, then by name, so a
// channel dropped between two others needs a position that puts it exactly there.
export interface Ordered { id: number; position: number; name: string }
export const byOrder = (a: Ordered, b: Ordered) => a.position - b.position || a.name.localeCompare(b.name);
// Position changes that put `moved` at `index` among `siblings` (which need not contain it).
// One change when there is room between the neighbours; otherwise the row is numbered again.
export function planMove(siblings: Ordered[], moved: Ordered, index: number): { id: number; position: number }[] {
const rest = siblings.filter(c => c.id !== moved.id).sort(byOrder);
const at = Math.max(0, Math.min(index, rest.length));
const prev = rest[at - 1];
const next = rest[at];
const fits = (position: number) => {
const m = { ...moved, position };
return (!prev || byOrder(prev, m) < 0) && (!next || byOrder(m, next) < 0);
};
const tries = [moved.position, prev?.position, next?.position, prev && prev.position + 1, next && next.position - 1];
for (const p of tries) {
if (p == null || !fits(p)) continue;
return p === moved.position ? [] : [{ id: moved.id, position: p }];
}
const order = [...rest.slice(0, at), moved, ...rest.slice(at)];
return order.map((c, i) => ({ id: c.id, position: i })).filter((u, i) => order[i].position !== u.position);
}