Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57036bb6a4 | ||
|
|
400ee76b63 | ||
|
|
47c1e435b7 | ||
|
|
d55a8ba8a5 | ||
|
|
2700aad209 |
@@ -40,6 +40,7 @@ import { creatorsSupported } from '../../../utils/matrix';
|
||||
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
||||
import { BreakWord } from '../../../styles/Text.css';
|
||||
import { useModalStyle } from '../../../hooks/useModalStyle';
|
||||
import { stateToCarry } from '../../../utils/roomUpgradeState';
|
||||
|
||||
function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
|
||||
const mx = useMatrixClient();
|
||||
@@ -63,10 +64,41 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
|
||||
const [upgradeState, upgrade] = useAsyncCallback(
|
||||
useCallback(
|
||||
async (version: string, newAdditionalCreators?: string[]) => {
|
||||
await mx.http.authedRequest(Method.Post, `/rooms/${room.roomId}/upgrade`, undefined, {
|
||||
new_version: version,
|
||||
additional_creators: newAdditionalCreators,
|
||||
});
|
||||
// [Gitea #327] Synapse doesn't copy Lotus room state (voice limit, call
|
||||
// policy, soundboard, retention, role tags, emoji packs) into the
|
||||
// replacement room: read it first, re-create it after the upgrade.
|
||||
const carried = stateToCarry(
|
||||
Array.from(room.currentState.events.values()).flatMap((byKey) =>
|
||||
Array.from(byKey.values()),
|
||||
),
|
||||
);
|
||||
const res = await mx.http.authedRequest<{ replacement_room?: string }>(
|
||||
Method.Post,
|
||||
`/rooms/${room.roomId}/upgrade`,
|
||||
undefined,
|
||||
{
|
||||
new_version: version,
|
||||
additional_creators: newAdditionalCreators,
|
||||
},
|
||||
);
|
||||
const newRoomId = res?.replacement_room;
|
||||
if (!newRoomId || carried.length === 0) return;
|
||||
const results = await Promise.allSettled(
|
||||
carried.map((st) =>
|
||||
mx.http.authedRequest(
|
||||
Method.Put,
|
||||
`/rooms/${encodeURIComponent(newRoomId)}/state/${encodeURIComponent(st.type)}/${encodeURIComponent(st.stateKey)}`,
|
||||
undefined,
|
||||
st.content,
|
||||
),
|
||||
),
|
||||
);
|
||||
const failed = results.filter((r) => r.status === 'rejected').length;
|
||||
if (failed > 0) {
|
||||
throw new Error(
|
||||
`Room upgraded, but ${failed} of ${carried.length} Lotus settings (voice limit, call policy, soundboard, retention…) could not be copied to the new room.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
[mx, room],
|
||||
),
|
||||
|
||||
@@ -51,7 +51,7 @@ const dark: CSSProperties = {
|
||||
backgroundSize: '260% 240%, 300% 260%, 240% 280%, 220% 200%, 100% 100%, 100% 100%',
|
||||
backgroundPosition: '0% 30%, 100% 70%, 50% 0%, 20% 80%, 50% 50%, 50% 50%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
animation: `${auroraFlow} 60s ease-in-out infinite`,
|
||||
animation: `${auroraFlow} 60s steps(180) infinite`,
|
||||
};
|
||||
|
||||
const light: CSSProperties = {
|
||||
@@ -75,7 +75,7 @@ const light: CSSProperties = {
|
||||
backgroundSize: '260% 240%, 300% 260%, 240% 280%, 220% 200%, 100% 100%, 100% 100%',
|
||||
backgroundPosition: '0% 30%, 100% 70%, 50% 0%, 20% 80%, 50% 50%, 50% 50%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
animation: `${auroraFlow} 60s ease-in-out infinite`,
|
||||
animation: `${auroraFlow} 60s steps(180) infinite`,
|
||||
};
|
||||
|
||||
export const animAurora: ChatBgVariants = { dark, light };
|
||||
|
||||
@@ -55,7 +55,7 @@ export const animFireflies: ChatBgVariants = {
|
||||
'0 0', // wash A (static)
|
||||
'0 0', // wash B (static)
|
||||
].join(','),
|
||||
animation: `${firefliesDrift} 44s linear infinite`,
|
||||
animation: `${firefliesDrift} 44s steps(528) infinite`,
|
||||
},
|
||||
|
||||
// Light: a cozy warm dim-dusk. No harsh dots on white — soft amber motes with
|
||||
@@ -93,6 +93,6 @@ export const animFireflies: ChatBgVariants = {
|
||||
'0 0', // wash A (static)
|
||||
'0 0', // wash B (static)
|
||||
].join(','),
|
||||
animation: `${firefliesDrift} 44s linear infinite`,
|
||||
animation: `${firefliesDrift} 44s steps(528) infinite`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ export const animPulse: ChatBgVariants = {
|
||||
'0 0', // base wash
|
||||
'0 0', // vignette
|
||||
].join(','),
|
||||
animation: `${gridPulse} 22s ease-in-out infinite`,
|
||||
animation: `${gridPulse} 22s steps(528) infinite`,
|
||||
},
|
||||
|
||||
// Light: soft slate-blue grid on pale cool-white, a gentle luminance breathe.
|
||||
@@ -116,6 +116,6 @@ export const animPulse: ChatBgVariants = {
|
||||
'0 0', // base wash
|
||||
'0 0', // vignette
|
||||
].join(','),
|
||||
animation: `${gridPulse} 22s ease-in-out infinite`,
|
||||
animation: `${gridPulse} 22s steps(528) infinite`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -97,7 +97,7 @@ export const animRain: ChatBgVariants = {
|
||||
].join(','),
|
||||
backgroundSize: ['260px 200px', '100% 100%', '100% 100%', '100% 100%'].join(','),
|
||||
backgroundPosition: ['0 0', '0 0', '0 0', '0 0'].join(','),
|
||||
animation: `${rainFall} 12s linear infinite`,
|
||||
animation: `${rainFall} 12s steps(288) infinite`,
|
||||
},
|
||||
|
||||
// Light: soft teal-grey streaks on a pale cool base — elegant, never neon.
|
||||
@@ -118,6 +118,6 @@ export const animRain: ChatBgVariants = {
|
||||
].join(','),
|
||||
backgroundSize: ['260px 200px', '100% 100%', '100% 100%', '100% 100%'].join(','),
|
||||
backgroundPosition: ['0 0', '0 0', '0 0', '0 0'].join(','),
|
||||
animation: `${rainFall} 12s linear infinite`,
|
||||
animation: `${rainFall} 12s steps(288) infinite`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ export const animStars: ChatBgVariants = {
|
||||
'0 0', // nebula A
|
||||
'0 0', // nebula B
|
||||
].join(','),
|
||||
animation: `${starDrift} 90s linear infinite`,
|
||||
animation: `${starDrift} 90s steps(1080) infinite`,
|
||||
},
|
||||
|
||||
// Light: an airy pre-dawn sky. No literal white-on-white stars — instead very
|
||||
@@ -112,6 +112,6 @@ export const animStars: ChatBgVariants = {
|
||||
'0 0', // wash A
|
||||
'0 0', // wash B
|
||||
].join(','),
|
||||
animation: `${starDrift} 100s linear infinite`,
|
||||
animation: `${starDrift} 100s steps(1200) infinite`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -149,6 +149,11 @@ export const getChatBg = (
|
||||
}
|
||||
// For animated backgrounds, promote the element to its own compositor layer so
|
||||
// background-position keyframes don't trigger repaints on descendant elements.
|
||||
// [Gitea #337] background-position can't run on the compositor, so every
|
||||
// animation frame repaints the whole chat area (45 to 70% main-thread busy
|
||||
// at 60 fps). The drifts are slow (3–22 px/s), so each animation uses steps():
|
||||
// ~12 updates/s for the slow ones, ~24/s for rain and pulse: visually the
|
||||
// same, a fraction of the repaints.
|
||||
if (style.animation) {
|
||||
return { ...style, willChange: 'background-position', contain: 'paint' };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { isCarriedStateType, stateToCarry } from './roomUpgradeState';
|
||||
|
||||
const ev = (type: string, content: Record<string, unknown>, stateKey = '') => ({
|
||||
getType: () => type,
|
||||
getStateKey: () => stateKey,
|
||||
getContent: () => content,
|
||||
});
|
||||
|
||||
test('Lotus room state is carried; state Synapse already copies is not (#327)', () => {
|
||||
assert.equal(isCarriedStateType('io.lotus.voice_limit'), true);
|
||||
assert.equal(isCarriedStateType('io.lotus.room_quality'), true);
|
||||
assert.equal(isCarriedStateType('io.lotus.soundboard'), true);
|
||||
assert.equal(isCarriedStateType('m.room.retention'), true);
|
||||
assert.equal(isCarriedStateType('in.cinny.room.power_level_tags'), true);
|
||||
assert.equal(isCarriedStateType('im.ponies.room_emotes'), true);
|
||||
assert.equal(isCarriedStateType('m.room.power_levels'), false);
|
||||
assert.equal(isCarriedStateType('m.room.name'), false);
|
||||
assert.equal(isCarriedStateType('m.room.member'), false);
|
||||
});
|
||||
|
||||
test('stateToCarry keeps state keys and skips emptied events', () => {
|
||||
const out = stateToCarry([
|
||||
ev('io.lotus.voice_limit', { max_users: 5 }),
|
||||
ev('io.lotus.soundboard', { clips: [1] }, 'pack-a'),
|
||||
ev('io.lotus.room_quality', {}),
|
||||
ev('m.room.name', { name: 'x' }),
|
||||
ev('m.room.retention', { max_lifetime: 604800000 }),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
out.map((s) => `${s.type}|${s.stateKey}`),
|
||||
['io.lotus.voice_limit|', 'io.lotus.soundboard|pack-a', 'm.room.retention|'],
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* [Gitea #327] Room state Synapse does NOT copy into the replacement room on
|
||||
* `POST /rooms/{id}/upgrade` (it carries only power levels, join rules,
|
||||
* name/topic/avatar, history visibility, guest access, encryption, server ACL
|
||||
* and space parent/child). Lotus keeps call policy, voice limits, soundboard,
|
||||
* message retention, role tags and room emoji packs in state, so an upgrade
|
||||
* silently dropped all of them.
|
||||
*/
|
||||
const CARRIED_TYPES = new Set([
|
||||
'm.room.retention',
|
||||
'in.cinny.room.power_level_tags',
|
||||
'im.ponies.room_emotes',
|
||||
]);
|
||||
const CARRIED_PREFIXES = ['io.lotus.'];
|
||||
|
||||
export type StateToCarry = { type: string; stateKey: string; content: Record<string, unknown> };
|
||||
|
||||
export const isCarriedStateType = (type: string): boolean =>
|
||||
CARRIED_TYPES.has(type) || CARRIED_PREFIXES.some((p) => type.startsWith(p));
|
||||
|
||||
/** The state events to re-create in the replacement room (non-empty content only). */
|
||||
export const stateToCarry = (
|
||||
events: Array<{
|
||||
getType: () => string;
|
||||
getStateKey: () => string | undefined;
|
||||
getContent: () => Record<string, unknown>;
|
||||
}>,
|
||||
): StateToCarry[] =>
|
||||
events
|
||||
.filter((ev) => isCarriedStateType(ev.getType()))
|
||||
.map((ev) => ({
|
||||
type: ev.getType(),
|
||||
stateKey: ev.getStateKey() ?? '',
|
||||
content: ev.getContent(),
|
||||
}))
|
||||
.filter((s) => s.content && Object.keys(s.content).length > 0);
|
||||
Reference in New Issue
Block a user