Post-merge fixes: remove duplicate IncomingCallNotification, restore PiP touch drag + grip dots, show redacted message content
- Router.tsx: remove IncomingCallNotification (CallEmbedProvider.IncomingCallListener now handles all calls) - CallEmbedProvider: restore touch drag (handlePipTouchStart), grip dots on resize handles, fix normaliseToTopLeft width/height - FallbackContent/MsgTypeRenderers: add originalBody prop to show struck-through original text on deleted messages - RoomTimeline: cache text message bodies so they can be shown after redaction
This commit is contained in:
@@ -68,6 +68,8 @@ function normaliseToTopLeft(el: HTMLElement) {
|
|||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
el.style.left = `${rect.left}px`;
|
el.style.left = `${rect.left}px`;
|
||||||
el.style.top = `${rect.top}px`;
|
el.style.top = `${rect.top}px`;
|
||||||
|
el.style.width = `${rect.width}px`;
|
||||||
|
el.style.height = `${rect.height}px`;
|
||||||
el.style.right = 'auto';
|
el.style.right = 'auto';
|
||||||
el.style.bottom = 'auto';
|
el.style.bottom = 'auto';
|
||||||
}
|
}
|
||||||
@@ -431,6 +433,34 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePipTouchStart = (e: React.TouchEvent) => {
|
||||||
|
const el = callEmbedRef.current;
|
||||||
|
if (!el || e.touches.length !== 1) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
pipDragRef.current = { startX: touch.clientX, startY: touch.clientY, origLeft: rect.left, origTop: rect.top, dragged: false };
|
||||||
|
const onTouchMove = (ev: TouchEvent) => {
|
||||||
|
if (!pipDragRef.current || !el || ev.touches.length !== 1) return;
|
||||||
|
ev.preventDefault();
|
||||||
|
const t = ev.touches[0];
|
||||||
|
const dx = t.clientX - pipDragRef.current.startX;
|
||||||
|
const dy = t.clientY - pipDragRef.current.startY;
|
||||||
|
if (!pipDragRef.current.dragged && Math.abs(dx) + Math.abs(dy) > 5) pipDragRef.current.dragged = true;
|
||||||
|
if (pipDragRef.current.dragged) {
|
||||||
|
el.style.left = `${Math.max(0, Math.min(window.innerWidth - el.offsetWidth, pipDragRef.current.origLeft + dx))}px`;
|
||||||
|
el.style.top = `${Math.max(0, Math.min(window.innerHeight - el.offsetHeight, pipDragRef.current.origTop + dy))}px`;
|
||||||
|
el.style.right = 'auto'; el.style.bottom = 'auto';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onTouchEnd = () => {
|
||||||
|
document.removeEventListener('touchmove', onTouchMove);
|
||||||
|
document.removeEventListener('touchend', onTouchEnd);
|
||||||
|
setTimeout(() => { if (pipDragRef.current) pipDragRef.current.dragged = false; }, 0);
|
||||||
|
};
|
||||||
|
document.addEventListener('touchmove', onTouchMove, { passive: false });
|
||||||
|
document.addEventListener('touchend', onTouchEnd);
|
||||||
|
};
|
||||||
|
|
||||||
const handleResizeMouseDown = (e: React.MouseEvent, corner: Corner) => {
|
const handleResizeMouseDown = (e: React.MouseEvent, corner: Corner) => {
|
||||||
e.stopPropagation(); e.preventDefault();
|
e.stopPropagation(); e.preventDefault();
|
||||||
const el = callEmbedRef.current; if (!el) return;
|
const el = callEmbedRef.current; if (!el) return;
|
||||||
@@ -477,6 +507,7 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="Return to call"
|
aria-label="Return to call"
|
||||||
onMouseDown={handlePipMouseDown}
|
onMouseDown={handlePipMouseDown}
|
||||||
|
onTouchStart={handlePipTouchStart}
|
||||||
onClick={() => { if (!pipDragRef.current?.dragged) navigateRoom(callEmbed.roomId); }}
|
onClick={() => { if (!pipDragRef.current?.dragged) navigateRoom(callEmbed.roomId); }}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && navigateRoom(callEmbed.roomId)}
|
onKeyDown={(e) => e.key === 'Enter' && navigateRoom(callEmbed.roomId)}
|
||||||
style={{ position:'absolute', inset:0, zIndex:1, background:'transparent', cursor:'grab', display:'flex', alignItems:'flex-start', justifyContent:'flex-end', padding:'6px' }}
|
style={{ position:'absolute', inset:0, zIndex:1, background:'transparent', cursor:'grab', display:'flex', alignItems:'flex-start', justifyContent:'flex-end', padding:'6px' }}
|
||||||
@@ -485,10 +516,20 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
↗ Return to call
|
↗ Return to call
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(['se','sw','ne','nw'] as Corner[]).map((corner) => (
|
{(['se','sw','ne','nw'] as Corner[]).map((corner) => {
|
||||||
<div key={corner} onMouseDown={(e) => handleResizeMouseDown(e, corner)} onClick={(e) => e.stopPropagation()}
|
const s = corner.includes('s'); const e2 = corner.includes('e');
|
||||||
style={{ position:'absolute', width:'18px', height:'18px', [corner.includes('s')?'bottom':'top']:0, [corner.includes('e')?'right':'left']:0, cursor:`${corner}-resize`, zIndex:2 }} />
|
const dots = [[2,2],[2,7],[7,2]].map(([a,b]) => ({
|
||||||
))}
|
position:'absolute' as const, width:4, height:4, borderRadius:'50%',
|
||||||
|
background:'rgba(255,255,255,0.45)',
|
||||||
|
[s?'bottom':'top']:a, [e2?'right':'left']:b,
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<div key={corner} onMouseDown={(ev) => handleResizeMouseDown(ev, corner)} onClick={(ev) => ev.stopPropagation()}
|
||||||
|
style={{ position:'absolute', width:'18px', height:'18px', [s?'bottom':'top']:0, [e2?'right':'left']:0, cursor:`${corner}-resize`, zIndex:2 }}>
|
||||||
|
{dots.map((style, i) => <div key={i} style={style} />)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,11 +40,12 @@ export function MBadEncrypted() {
|
|||||||
|
|
||||||
type RedactedContentProps = {
|
type RedactedContentProps = {
|
||||||
reason?: string;
|
reason?: string;
|
||||||
|
originalBody?: string;
|
||||||
};
|
};
|
||||||
export function RedactedContent({ reason }: RedactedContentProps) {
|
export function RedactedContent({ reason, originalBody }: RedactedContentProps) {
|
||||||
return (
|
return (
|
||||||
<Text>
|
<Text>
|
||||||
<MessageDeletedContent reason={reason} />
|
<MessageDeletedContent reason={reason} originalBody={originalBody} />
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import React from 'react';
|
|||||||
const warningStyle = { color: color.Warning.Main, opacity: config.opacity.P300 };
|
const warningStyle = { color: color.Warning.Main, opacity: config.opacity.P300 };
|
||||||
const criticalStyle = { color: color.Critical.Main, opacity: config.opacity.P300 };
|
const criticalStyle = { color: color.Critical.Main, opacity: config.opacity.P300 };
|
||||||
|
|
||||||
export const MessageDeletedContent = as<'div', { children?: never; reason?: string }>(
|
export const MessageDeletedContent = as<'div', { children?: never; reason?: string; originalBody?: string }>(
|
||||||
({ reason, ...props }, ref) => (
|
({ reason, originalBody, ...props }, ref) => (
|
||||||
<Box as="span" alignItems="Center" gap="100" style={warningStyle} {...props} ref={ref}>
|
<Box as="span" alignItems="Center" gap="100" style={warningStyle} {...props} ref={ref}>
|
||||||
<Icon size="50" src={Icons.Delete} />
|
<Icon size="50" src={Icons.Delete} />
|
||||||
{reason ? (
|
{originalBody ? (
|
||||||
|
<s>{originalBody.length > 80 ? `${originalBody.slice(0, 80)}…` : originalBody}</s>
|
||||||
|
) : reason ? (
|
||||||
<i>This message has been deleted. {reason}</i>
|
<i>This message has been deleted. {reason}</i>
|
||||||
) : (
|
) : (
|
||||||
<i>This message has been deleted</i>
|
<i>This message has been deleted</i>
|
||||||
|
|||||||
@@ -506,6 +506,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
smooth: true,
|
smooth: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const redactedBodyCache = useRef<Map<string, string>>(new Map());
|
||||||
|
|
||||||
const [focusItem, setFocusItem] = useState<
|
const [focusItem, setFocusItem] = useState<
|
||||||
| {
|
| {
|
||||||
index: number;
|
index: number;
|
||||||
@@ -1035,6 +1037,14 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
const getContent = (() =>
|
const getContent = (() =>
|
||||||
editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent()) as GetContentCallback;
|
editedEvent?.getContent()['m.new_content'] ?? mEvent.getContent()) as GetContentCallback;
|
||||||
|
|
||||||
|
// Cache body before it can be stripped by redaction
|
||||||
|
if (!mEvent.isRedacted()) {
|
||||||
|
const c = mEvent.getContent();
|
||||||
|
if (c.body && ['m.text', 'm.notice', 'm.emote'].includes(c.msgtype ?? '')) {
|
||||||
|
redactedBodyCache.current.set(mEventId, c.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
const senderDisplayName =
|
const senderDisplayName =
|
||||||
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
|
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
|
||||||
@@ -1096,7 +1106,10 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
dateFormatString={dateFormatString}
|
dateFormatString={dateFormatString}
|
||||||
>
|
>
|
||||||
{mEvent.isRedacted() ? (
|
{mEvent.isRedacted() ? (
|
||||||
<RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
|
<RedactedContent
|
||||||
|
reason={mEvent.getUnsigned().redacted_because?.content.reason}
|
||||||
|
originalBody={redactedBodyCache.current.get(mEventId)}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RenderMessageContent
|
<RenderMessageContent
|
||||||
displayName={senderDisplayName}
|
displayName={senderDisplayName}
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ import { CreateSpaceModalRenderer } from '../features/create-space';
|
|||||||
import { SearchModalRenderer } from '../features/search';
|
import { SearchModalRenderer } from '../features/search';
|
||||||
import { getFallbackSession } from '../state/sessions';
|
import { getFallbackSession } from '../state/sessions';
|
||||||
import { CallStatusRenderer } from './CallStatusRenderer';
|
import { CallStatusRenderer } from './CallStatusRenderer';
|
||||||
import { IncomingCallNotification } from '../components/IncomingCallNotification';
|
|
||||||
import { CallEmbedProvider } from '../components/CallEmbedProvider';
|
import { CallEmbedProvider } from '../components/CallEmbedProvider';
|
||||||
|
|
||||||
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) => {
|
||||||
@@ -138,7 +137,6 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||||||
<Outlet />
|
<Outlet />
|
||||||
</ClientLayout>
|
</ClientLayout>
|
||||||
<CallStatusRenderer />
|
<CallStatusRenderer />
|
||||||
<IncomingCallNotification />
|
|
||||||
</CallEmbedProvider>
|
</CallEmbedProvider>
|
||||||
<SearchModalRenderer />
|
<SearchModalRenderer />
|
||||||
<UserRoomProfileRenderer />
|
<UserRoomProfileRenderer />
|
||||||
|
|||||||
Reference in New Issue
Block a user