Widen the media panel, support skinny full-page mode, keep jump-to-latest and return-to-previous reliable, and stop same-room event jumps from remounting the outlet or yanking the sidebar.
65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { Outlet, useLocation } from 'react-router-dom';
|
|
|
|
const decodeSegment = (segment: string): string => {
|
|
let decoded = segment;
|
|
try {
|
|
let next = decodeURIComponent(decoded);
|
|
while (next !== decoded) {
|
|
decoded = next;
|
|
next = decodeURIComponent(decoded);
|
|
}
|
|
} catch {
|
|
// keep partially decoded value
|
|
}
|
|
return decoded;
|
|
};
|
|
|
|
/**
|
|
* Room routes are `:roomIdOrAlias/:eventId?/`. Jumping to an event (or clearing it)
|
|
* changes the pathname but not the room — keep the outlet mounted so we don't replay
|
|
* the route enter animation or remount drawers like Shared Media.
|
|
*
|
|
* Path segments are decoded so `!room:server` and `%21room%3Aserver` share a key.
|
|
*/
|
|
const getOutletTransitionKey = (pathname: string): string => {
|
|
const segments = pathname.split('/').filter(Boolean).map(decodeSegment);
|
|
if (segments.length === 0) return pathname;
|
|
|
|
// Matrix event IDs start with `$`
|
|
if (segments[segments.length - 1].startsWith('$')) {
|
|
segments.pop();
|
|
}
|
|
|
|
return `/${segments.join('/')}/`;
|
|
};
|
|
|
|
/**
|
|
* Wrapper for Outlet that adds route-based animation.
|
|
* Remounts (and animates) when leaving a room / switching rooms, not on same-room event hops.
|
|
*/
|
|
export function AnimatedOutlet() {
|
|
const location = useLocation();
|
|
const transitionKey = useMemo(
|
|
() => getOutletTransitionKey(location.pathname),
|
|
[location.pathname]
|
|
);
|
|
|
|
return (
|
|
<div
|
|
key={transitionKey}
|
|
data-route-transition="true"
|
|
style={{
|
|
flex: 1,
|
|
minWidth: 0,
|
|
minHeight: 0,
|
|
overflow: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
<Outlet />
|
|
</div>
|
|
);
|
|
}
|