2024-07-18 18:50:20 +05:30
|
|
|
import React, { useCallback, useRef } from 'react';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { Box, Text, config } from 'folds';
|
2026-03-07 18:03:32 +11:00
|
|
|
import { EventType } from 'matrix-js-sdk';
|
2024-07-18 18:50:20 +05:30
|
|
|
import { ReactEditor } from 'slate-react';
|
|
|
|
|
import { isKeyHotkey } from 'is-hotkey';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { useStateEvent } from '../../hooks/useStateEvent';
|
|
|
|
|
import { StateEvent } from '../../../types/matrix/room';
|
2025-08-12 19:42:30 +05:30
|
|
|
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
|
|
|
import { useEditor } from '../../components/editor';
|
|
|
|
|
import { RoomInputPlaceholder } from './RoomInputPlaceholder';
|
|
|
|
|
import { RoomTimeline } from './RoomTimeline';
|
|
|
|
|
import { RoomViewTyping } from './RoomViewTyping';
|
|
|
|
|
import { RoomTombstone } from './RoomTombstone';
|
|
|
|
|
import { RoomInput } from './RoomInput';
|
2025-02-26 21:44:53 +11:00
|
|
|
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
|
2024-05-31 19:49:46 +05:30
|
|
|
import { Page } from '../../components/page';
|
2026-05-13 21:17:59 -04:00
|
|
|
import { useSetting } from '../../state/hooks/settings';
|
|
|
|
|
import { settingsAtom, ChatBackground } from '../../state/settings';
|
2024-07-18 18:50:20 +05:30
|
|
|
import { useKeyDown } from '../../hooks/useKeyDown';
|
|
|
|
|
import { editableActiveElement } from '../../utils/dom';
|
2025-02-26 21:44:53 +11:00
|
|
|
import { settingsAtom } from '../../state/settings';
|
|
|
|
|
import { useSetting } from '../../state/hooks/settings';
|
2025-08-12 19:42:30 +05:30
|
|
|
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
|
|
|
|
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
2026-03-07 18:03:32 +11:00
|
|
|
import { useRoom } from '../../hooks/useRoom';
|
2024-07-18 18:50:20 +05:30
|
|
|
|
2025-02-26 21:42:42 +11:00
|
|
|
const FN_KEYS_REGEX = /^F\d+$/;
|
2024-07-18 18:50:20 +05:30
|
|
|
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
|
|
|
|
const { code } = evt;
|
|
|
|
|
if (evt.metaKey || evt.altKey || evt.ctrlKey) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-08-04 11:06:42 +05:30
|
|
|
|
2025-02-26 21:42:42 +11:00
|
|
|
if (FN_KEYS_REGEX.test(code)) return false;
|
2024-07-18 18:50:20 +05:30
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
code.startsWith('OS') ||
|
|
|
|
|
code.startsWith('Meta') ||
|
|
|
|
|
code.startsWith('Shift') ||
|
|
|
|
|
code.startsWith('Alt') ||
|
|
|
|
|
code.startsWith('Control') ||
|
|
|
|
|
code.startsWith('Arrow') ||
|
2024-08-04 11:06:42 +05:30
|
|
|
code.startsWith('Page') ||
|
|
|
|
|
code.startsWith('End') ||
|
|
|
|
|
code.startsWith('Home') ||
|
2024-07-18 18:50:20 +05:30
|
|
|
code === 'Tab' ||
|
|
|
|
|
code === 'Space' ||
|
|
|
|
|
code === 'Enter' ||
|
|
|
|
|
code === 'NumLock' ||
|
|
|
|
|
code === 'ScrollLock'
|
|
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
2024-05-31 19:49:46 +05:30
|
|
|
|
2026-05-13 21:17:59 -04:00
|
|
|
|
|
|
|
|
const CHAT_BG: Record<ChatBackground, React.CSSProperties> = {
|
|
|
|
|
none: {},
|
2026-05-13 21:42:12 -04:00
|
|
|
|
|
|
|
|
// Dark navy with light-blue grid lines — technical/clean
|
|
|
|
|
blueprint: {
|
|
|
|
|
backgroundColor: '#0a1628',
|
|
|
|
|
backgroundImage: [
|
|
|
|
|
'linear-gradient(rgba(100,149,237,0.14) 1px, transparent 1px)',
|
|
|
|
|
'linear-gradient(90deg, rgba(100,149,237,0.14) 1px, transparent 1px)',
|
|
|
|
|
'linear-gradient(rgba(100,149,237,0.05) 1px, transparent 1px)',
|
|
|
|
|
'linear-gradient(90deg, rgba(100,149,237,0.05) 1px, transparent 1px)',
|
|
|
|
|
].join(','),
|
|
|
|
|
backgroundSize: '80px 80px, 80px 80px, 16px 16px, 16px 16px',
|
2026-05-13 21:17:59 -04:00
|
|
|
},
|
2026-05-13 21:42:12 -04:00
|
|
|
|
|
|
|
|
// Near-black diagonal weave — carbon fiber look
|
|
|
|
|
carbon: {
|
|
|
|
|
backgroundColor: '#0e0e0e',
|
|
|
|
|
backgroundImage: [
|
|
|
|
|
'repeating-linear-gradient(45deg, rgba(255,255,255,0.03) 0, rgba(255,255,255,0.03) 2px, transparent 0, transparent 50%)',
|
|
|
|
|
'repeating-linear-gradient(135deg, rgba(255,255,255,0.03) 0, rgba(255,255,255,0.03) 2px, transparent 0, transparent 50%)',
|
|
|
|
|
].join(','),
|
|
|
|
|
backgroundSize: '8px 8px',
|
2026-05-13 21:17:59 -04:00
|
|
|
},
|
2026-05-13 21:42:12 -04:00
|
|
|
|
|
|
|
|
// Deep space with three offset star layers
|
|
|
|
|
stars: {
|
|
|
|
|
backgroundColor: '#050510',
|
|
|
|
|
backgroundImage: [
|
|
|
|
|
'radial-gradient(circle, rgba(255,255,255,0.85) 1px, transparent 1px)',
|
|
|
|
|
'radial-gradient(circle, rgba(255,255,255,0.55) 1px, transparent 1px)',
|
|
|
|
|
'radial-gradient(circle, rgba(200,200,255,0.3) 1px, transparent 1px)',
|
|
|
|
|
].join(','),
|
|
|
|
|
backgroundSize: '130px 130px, 190px 190px, 260px 260px',
|
|
|
|
|
backgroundPosition: '0 0, 65px 32px, 32px 97px',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Concentric rings from three focal points — map contour effect
|
|
|
|
|
topographic: {
|
|
|
|
|
backgroundColor: '#0f0f17',
|
|
|
|
|
backgroundImage: [
|
|
|
|
|
'repeating-radial-gradient(circle at 20% 20%, transparent 0, transparent 30px, rgba(152,0,0,0.07) 31px, transparent 32px)',
|
|
|
|
|
'repeating-radial-gradient(circle at 80% 80%, transparent 0, transparent 25px, rgba(100,100,200,0.06) 26px, transparent 27px)',
|
|
|
|
|
'repeating-radial-gradient(circle at 50% 10%, transparent 0, transparent 45px, rgba(152,0,0,0.04) 46px, transparent 47px)',
|
|
|
|
|
].join(','),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 60°/120° diagonal stripe pair — classic herringbone weave
|
|
|
|
|
herringbone: {
|
|
|
|
|
backgroundColor: '#111118',
|
|
|
|
|
backgroundImage: [
|
|
|
|
|
'repeating-linear-gradient(60deg, rgba(180,160,210,0.07) 0, rgba(180,160,210,0.07) 1px, transparent 0, transparent 50%)',
|
|
|
|
|
'repeating-linear-gradient(120deg, rgba(180,160,210,0.07) 0, rgba(180,160,210,0.07) 1px, transparent 0, transparent 50%)',
|
|
|
|
|
].join(','),
|
|
|
|
|
backgroundSize: '20px 36px',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Fine white grid with larger subdivision — graph paper
|
|
|
|
|
crosshatch: {
|
|
|
|
|
backgroundColor: '#0f0f0f',
|
|
|
|
|
backgroundImage: [
|
|
|
|
|
'linear-gradient(rgba(255,255,255,0.06) 1px, transparent 1px)',
|
|
|
|
|
'linear-gradient(90deg, rgba(255,255,255,0.06) 1px, transparent 1px)',
|
|
|
|
|
'linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px)',
|
|
|
|
|
'linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px)',
|
|
|
|
|
].join(','),
|
|
|
|
|
backgroundSize: '60px 60px, 60px 60px, 12px 12px, 12px 12px',
|
2026-05-13 21:17:59 -04:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-07 18:03:32 +11:00
|
|
|
export function RoomView({ eventId }: { eventId?: string }) {
|
2024-07-18 18:50:20 +05:30
|
|
|
const roomInputRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const roomViewRef = useRef<HTMLDivElement>(null);
|
2026-05-13 21:17:59 -04:00
|
|
|
const [chatBackground] = useSetting(settingsAtom, 'chatBackground');
|
2024-05-31 19:49:46 +05:30
|
|
|
|
2025-02-26 21:44:53 +11:00
|
|
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
|
|
|
|
|
2026-03-07 18:03:32 +11:00
|
|
|
const room = useRoom();
|
2024-05-31 19:49:46 +05:30
|
|
|
const { roomId } = room;
|
|
|
|
|
const editor = useEditor();
|
|
|
|
|
|
|
|
|
|
const mx = useMatrixClient();
|
|
|
|
|
|
|
|
|
|
const tombstoneEvent = useStateEvent(room, StateEvent.RoomTombstone);
|
|
|
|
|
const powerLevels = usePowerLevelsContext();
|
2025-08-12 19:42:30 +05:30
|
|
|
const creators = useRoomCreators(room);
|
2024-05-31 19:49:46 +05:30
|
|
|
|
2025-08-12 19:42:30 +05:30
|
|
|
const permissions = useRoomPermissions(creators, powerLevels);
|
|
|
|
|
const canMessage = permissions.event(EventType.RoomMessage, mx.getSafeUserId());
|
2025-03-23 22:09:29 +11:00
|
|
|
|
2024-07-18 18:50:20 +05:30
|
|
|
useKeyDown(
|
|
|
|
|
window,
|
|
|
|
|
useCallback(
|
|
|
|
|
(evt) => {
|
|
|
|
|
if (editableActiveElement()) return;
|
2025-09-12 17:22:51 +05:30
|
|
|
const portalContainer = document.getElementById('portalContainer');
|
|
|
|
|
if (portalContainer && portalContainer.children.length > 0) {
|
2024-07-18 18:50:20 +05:30
|
|
|
return;
|
|
|
|
|
}
|
2024-07-21 11:13:33 +05:30
|
|
|
if (shouldFocusMessageField(evt) || isKeyHotkey('mod+v', evt)) {
|
2024-07-18 18:50:20 +05:30
|
|
|
ReactEditor.focus(editor);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[editor]
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2024-05-31 19:49:46 +05:30
|
|
|
return (
|
2026-05-13 21:17:59 -04:00
|
|
|
<Page ref={roomViewRef} style={CHAT_BG[chatBackground]}>
|
2024-05-31 19:49:46 +05:30
|
|
|
<Box grow="Yes" direction="Column">
|
|
|
|
|
<RoomTimeline
|
|
|
|
|
key={roomId}
|
|
|
|
|
room={room}
|
|
|
|
|
eventId={eventId}
|
|
|
|
|
roomInputRef={roomInputRef}
|
|
|
|
|
editor={editor}
|
|
|
|
|
/>
|
|
|
|
|
<RoomViewTyping room={room} />
|
|
|
|
|
</Box>
|
|
|
|
|
<Box shrink="No" direction="Column">
|
|
|
|
|
<div style={{ padding: `0 ${config.space.S400}` }}>
|
|
|
|
|
{tombstoneEvent ? (
|
|
|
|
|
<RoomTombstone
|
|
|
|
|
roomId={roomId}
|
|
|
|
|
body={tombstoneEvent.getContent().body}
|
|
|
|
|
replacementRoomId={tombstoneEvent.getContent().replacement_room}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{canMessage && (
|
|
|
|
|
<RoomInput
|
|
|
|
|
room={room}
|
|
|
|
|
editor={editor}
|
|
|
|
|
roomId={roomId}
|
|
|
|
|
fileDropContainerRef={roomViewRef}
|
|
|
|
|
ref={roomInputRef}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{!canMessage && (
|
|
|
|
|
<RoomInputPlaceholder
|
|
|
|
|
style={{ padding: config.space.S200 }}
|
|
|
|
|
alignItems="Center"
|
|
|
|
|
justifyContent="Center"
|
|
|
|
|
>
|
|
|
|
|
<Text align="Center">You do not have permission to post in this room</Text>
|
|
|
|
|
</RoomInputPlaceholder>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2025-02-26 21:44:53 +11:00
|
|
|
{hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />}
|
2024-05-31 19:49:46 +05:30
|
|
|
</Box>
|
|
|
|
|
</Page>
|
|
|
|
|
);
|
|
|
|
|
}
|