mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-09-13 22:32:26 +03:00
New create chat screen (#2463)
Some checks are pending
Deploy to Netlify (dev) / Deploy to Netlify (push) Waiting to run
Some checks are pending
Deploy to Netlify (dev) / Deploy to Netlify (push) Waiting to run
* fix dm invite appears in home * use migrated function for convert to dm/room commands * add new create chat screen
This commit is contained in:
parent
23aa5c6f94
commit
b4266c26b0
8 changed files with 246 additions and 66 deletions
|
@ -1,7 +1,8 @@
|
||||||
import { Box, Button, color, config, Icon, Icons, Spinner, Text } from 'folds';
|
import { Box, Button, config, Icon, Icons, Text } from 'folds';
|
||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { UserHero, UserHeroName } from './UserHero';
|
import { UserHero, UserHeroName } from './UserHero';
|
||||||
import { getDMRoomFor, getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
|
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
|
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
@ -9,11 +10,6 @@ import { usePowerLevels } from '../../hooks/usePowerLevels';
|
||||||
import { useRoom } from '../../hooks/useRoom';
|
import { useRoom } from '../../hooks/useRoom';
|
||||||
import { useUserPresence } from '../../hooks/useUserPresence';
|
import { useUserPresence } from '../../hooks/useUserPresence';
|
||||||
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
|
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
|
||||||
import { createDM } from '../../../client/action/room';
|
|
||||||
import { hasDevices } from '../../../util/matrixUtil';
|
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
|
||||||
import { useAlive } from '../../hooks/useAlive';
|
|
||||||
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
||||||
import { PowerChip } from './PowerChip';
|
import { PowerChip } from './PowerChip';
|
||||||
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
|
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
|
||||||
|
@ -24,6 +20,8 @@ import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||||
import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
|
import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
|
||||||
import { CreatorChip } from './CreatorChip';
|
import { CreatorChip } from './CreatorChip';
|
||||||
|
import { getDirectCreatePath, withSearchParam } from '../../pages/pathUtils';
|
||||||
|
import { DirectCreateSearchParams } from '../../pages/paths';
|
||||||
|
|
||||||
type UserRoomProfileProps = {
|
type UserRoomProfileProps = {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -31,8 +29,7 @@ type UserRoomProfileProps = {
|
||||||
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const { navigateRoom } = useRoomNavigate();
|
const navigate = useNavigate();
|
||||||
const alive = useAlive();
|
|
||||||
const closeUserRoomProfile = useCloseUserRoomProfile();
|
const closeUserRoomProfile = useCloseUserRoomProfile();
|
||||||
const ignoredUsers = useIgnoredUsers();
|
const ignoredUsers = useIgnoredUsers();
|
||||||
const ignored = ignoredUsers.includes(userId);
|
const ignored = ignoredUsers.includes(userId);
|
||||||
|
@ -62,26 +59,12 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
||||||
|
|
||||||
const presence = useUserPresence(userId);
|
const presence = useUserPresence(userId);
|
||||||
|
|
||||||
const [directMessageState, directMessage] = useAsyncCallback<string, Error, []>(
|
|
||||||
useCallback(async () => {
|
|
||||||
const result = await createDM(mx, userId, await hasDevices(mx, userId));
|
|
||||||
return result.room_id as string;
|
|
||||||
}, [userId, mx])
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMessage = () => {
|
const handleMessage = () => {
|
||||||
const dmRoomId = getDMRoomFor(mx, userId)?.roomId;
|
|
||||||
if (dmRoomId) {
|
|
||||||
navigateRoom(dmRoomId);
|
|
||||||
closeUserRoomProfile();
|
closeUserRoomProfile();
|
||||||
return;
|
const directSearchParam: DirectCreateSearchParams = {
|
||||||
}
|
userId,
|
||||||
directMessage().then((rId) => {
|
};
|
||||||
if (alive()) {
|
navigate(withSearchParam(getDirectCreatePath(), directSearchParam));
|
||||||
navigateRoom(rId);
|
|
||||||
closeUserRoomProfile();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -102,14 +85,7 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
||||||
variant="Primary"
|
variant="Primary"
|
||||||
fill="Solid"
|
fill="Solid"
|
||||||
radii="300"
|
radii="300"
|
||||||
disabled={directMessageState.status === AsyncStatus.Loading}
|
before={<Icon size="50" src={Icons.Message} filled />}
|
||||||
before={
|
|
||||||
directMessageState.status === AsyncStatus.Loading ? (
|
|
||||||
<Spinner size="50" variant="Primary" fill="Solid" />
|
|
||||||
) : (
|
|
||||||
<Icon size="50" src={Icons.Message} filled />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={handleMessage}
|
onClick={handleMessage}
|
||||||
>
|
>
|
||||||
<Text size="B300">Message</Text>
|
<Text size="B300">Message</Text>
|
||||||
|
@ -117,11 +93,6 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{directMessageState.status === AsyncStatus.Error && (
|
|
||||||
<Text style={{ color: color.Critical.Main }}>
|
|
||||||
<b>{directMessageState.error.message}</b>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Box alignItems="Center" gap="200" wrap="Wrap">
|
<Box alignItems="Center" gap="200" wrap="Wrap">
|
||||||
{server && <ServerChip server={server} />}
|
{server && <ServerChip server={server} />}
|
||||||
<ShareChip userId={userId} />
|
<ShareChip userId={userId} />
|
||||||
|
|
150
src/app/features/create-chat/CreateChat.tsx
Normal file
150
src/app/features/create-chat/CreateChat.tsx
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import { Box, Button, color, config, Icon, Icons, Input, Spinner, Switch, Text } from 'folds';
|
||||||
|
import React, { FormEventHandler, useCallback, useState } from 'react';
|
||||||
|
import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { SettingTile } from '../../components/setting-tile';
|
||||||
|
import { SequenceCard } from '../../components/sequence-card';
|
||||||
|
import { addRoomIdToMDirect, isUserId } from '../../utils/matrix';
|
||||||
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
|
import { ErrorCode } from '../../cs-errorcode';
|
||||||
|
import { millisecondsToMinutes } from '../../utils/common';
|
||||||
|
import { createRoomEncryptionState } from '../../components/create-room';
|
||||||
|
import { useAlive } from '../../hooks/useAlive';
|
||||||
|
import { getDirectRoomPath } from '../../pages/pathUtils';
|
||||||
|
|
||||||
|
type CreateChatProps = {
|
||||||
|
defaultUserId?: string;
|
||||||
|
};
|
||||||
|
export function CreateChat({ defaultUserId }: CreateChatProps) {
|
||||||
|
const mx = useMatrixClient();
|
||||||
|
const alive = useAlive();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [encryption, setEncryption] = useState(true);
|
||||||
|
const [invalidUserId, setInvalidUserId] = useState(false);
|
||||||
|
|
||||||
|
const [createState, create] = useAsyncCallback<string, Error | MatrixError, [string, boolean]>(
|
||||||
|
useCallback(
|
||||||
|
async (userId, encrypted) => {
|
||||||
|
const initialState: ICreateRoomStateEvent[] = [];
|
||||||
|
|
||||||
|
if (encrypted) initialState.push(createRoomEncryptionState());
|
||||||
|
|
||||||
|
const result = await mx.createRoom({
|
||||||
|
is_direct: true,
|
||||||
|
invite: [userId],
|
||||||
|
visibility: Visibility.Private,
|
||||||
|
preset: Preset.TrustedPrivateChat,
|
||||||
|
initial_state: initialState,
|
||||||
|
});
|
||||||
|
|
||||||
|
addRoomIdToMDirect(mx, result.room_id, userId);
|
||||||
|
|
||||||
|
return result.room_id;
|
||||||
|
},
|
||||||
|
[mx]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const loading = createState.status === AsyncStatus.Loading;
|
||||||
|
const error = createState.status === AsyncStatus.Error ? createState.error : undefined;
|
||||||
|
const disabled = createState.status === AsyncStatus.Loading;
|
||||||
|
|
||||||
|
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
setInvalidUserId(false);
|
||||||
|
|
||||||
|
const target = evt.target as HTMLFormElement | undefined;
|
||||||
|
const userIdInput = target?.userIdInput as HTMLInputElement | undefined;
|
||||||
|
const userId = userIdInput?.value.trim();
|
||||||
|
|
||||||
|
if (!userIdInput || !userId) return;
|
||||||
|
if (!isUserId(userId)) {
|
||||||
|
setInvalidUserId(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(userId, encryption).then((roomId) => {
|
||||||
|
if (alive()) {
|
||||||
|
userIdInput.value = '';
|
||||||
|
navigate(getDirectRoomPath(roomId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap="500">
|
||||||
|
<Box direction="Column" gap="100">
|
||||||
|
<Text size="L400">User ID</Text>
|
||||||
|
<Input
|
||||||
|
defaultValue={defaultUserId}
|
||||||
|
placeholder="@john:server"
|
||||||
|
name="userIdInput"
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
size="500"
|
||||||
|
radii="400"
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
{invalidUserId && (
|
||||||
|
<Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100">
|
||||||
|
<Icon src={Icons.Warning} filled size="50" />
|
||||||
|
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||||
|
<b>Please enter a valid User ID.</b>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box shrink="No" direction="Column" gap="100">
|
||||||
|
<Text size="L400">Options</Text>
|
||||||
|
<SequenceCard
|
||||||
|
style={{ padding: config.space.S300 }}
|
||||||
|
variant="SurfaceVariant"
|
||||||
|
direction="Column"
|
||||||
|
gap="500"
|
||||||
|
>
|
||||||
|
<SettingTile
|
||||||
|
title="End-to-End Encryption"
|
||||||
|
description="Once this feature is enabled, it can't be disabled after the room is created."
|
||||||
|
after={
|
||||||
|
<Switch
|
||||||
|
variant="Primary"
|
||||||
|
value={encryption}
|
||||||
|
onChange={setEncryption}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SequenceCard>
|
||||||
|
</Box>
|
||||||
|
{error && (
|
||||||
|
<Box style={{ color: color.Critical.Main }} alignItems="Center" gap="200">
|
||||||
|
<Icon src={Icons.Warning} filled size="100" />
|
||||||
|
<Text size="T300" style={{ color: color.Critical.Main }}>
|
||||||
|
<b>
|
||||||
|
{error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED
|
||||||
|
? `Server rate-limited your request for ${millisecondsToMinutes(
|
||||||
|
(error.data.retry_after_ms as number | undefined) ?? 0
|
||||||
|
)} minutes!`
|
||||||
|
: error.message}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box shrink="No" direction="Column" gap="200">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="500"
|
||||||
|
variant="Primary"
|
||||||
|
radii="400"
|
||||||
|
disabled={disabled}
|
||||||
|
before={loading && <Spinner variant="Primary" fill="Solid" size="200" />}
|
||||||
|
>
|
||||||
|
<Text size="B500">Create</Text>
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
1
src/app/features/create-chat/index.ts
Normal file
1
src/app/features/create-chat/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './CreateChat';
|
|
@ -2,12 +2,15 @@ import { Direction, IContextResponse, MatrixClient, Method, Room, RoomMember } f
|
||||||
import { RoomServerAclEventContent } from 'matrix-js-sdk/lib/types';
|
import { RoomServerAclEventContent } from 'matrix-js-sdk/lib/types';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
|
addRoomIdToMDirect,
|
||||||
getDMRoomFor,
|
getDMRoomFor,
|
||||||
|
guessDmRoomUserId,
|
||||||
isRoomAlias,
|
isRoomAlias,
|
||||||
isRoomId,
|
isRoomId,
|
||||||
isServerName,
|
isServerName,
|
||||||
isUserId,
|
isUserId,
|
||||||
rateLimitedActions,
|
rateLimitedActions,
|
||||||
|
removeRoomIdFromMDirect,
|
||||||
} from '../utils/matrix';
|
} from '../utils/matrix';
|
||||||
import { hasDevices } from '../../util/matrixUtil';
|
import { hasDevices } from '../../util/matrixUtil';
|
||||||
import * as roomActions from '../../client/action/room';
|
import * as roomActions from '../../client/action/room';
|
||||||
|
@ -348,14 +351,15 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
|
||||||
name: Command.ConvertToDm,
|
name: Command.ConvertToDm,
|
||||||
description: 'Convert room to direct message',
|
description: 'Convert room to direct message',
|
||||||
exe: async () => {
|
exe: async () => {
|
||||||
roomActions.convertToDm(mx, room.roomId);
|
const dmUserId = guessDmRoomUserId(room, mx.getSafeUserId());
|
||||||
|
await addRoomIdToMDirect(mx, room.roomId, dmUserId);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Command.ConvertToRoom]: {
|
[Command.ConvertToRoom]: {
|
||||||
name: Command.ConvertToRoom,
|
name: Command.ConvertToRoom,
|
||||||
description: 'Convert direct message to room',
|
description: 'Convert direct message to room',
|
||||||
exe: async () => {
|
exe: async () => {
|
||||||
roomActions.convertToRoom(mx, room.roomId);
|
await removeRoomIdFromMDirect(mx, room.roomId);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Command.Delete]: {
|
[Command.Delete]: {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { factoryRoomIdByActivity } from '../../../utils/sort';
|
import { factoryRoomIdByActivity } from '../../../utils/sort';
|
||||||
import {
|
import {
|
||||||
|
@ -28,7 +29,7 @@ import {
|
||||||
NavItem,
|
NavItem,
|
||||||
NavItemContent,
|
NavItemContent,
|
||||||
} from '../../../components/nav';
|
} from '../../../components/nav';
|
||||||
import { getDirectRoomPath } from '../../pathUtils';
|
import { getDirectCreatePath, getDirectRoomPath } from '../../pathUtils';
|
||||||
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
|
||||||
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
|
||||||
import { VirtualTile } from '../../../components/virtualizer';
|
import { VirtualTile } from '../../../components/virtualizer';
|
||||||
|
@ -38,7 +39,6 @@ import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
|
||||||
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
|
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
|
||||||
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
|
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
|
||||||
import { useDirectRooms } from './useDirectRooms';
|
import { useDirectRooms } from './useDirectRooms';
|
||||||
import { openInviteUser } from '../../../../client/action/navigation';
|
|
||||||
import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page';
|
import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page';
|
||||||
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
|
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
|
||||||
import { useRoomsUnread } from '../../../state/hooks/unread';
|
import { useRoomsUnread } from '../../../state/hooks/unread';
|
||||||
|
@ -50,6 +50,7 @@ import {
|
||||||
getRoomNotificationMode,
|
getRoomNotificationMode,
|
||||||
useRoomsNotificationPreferencesContext,
|
useRoomsNotificationPreferencesContext,
|
||||||
} from '../../../hooks/useRoomsNotificationPreferences';
|
} from '../../../hooks/useRoomsNotificationPreferences';
|
||||||
|
import { useDirectCreateSelected } from '../../../hooks/router/useDirectSelected';
|
||||||
|
|
||||||
type DirectMenuProps = {
|
type DirectMenuProps = {
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
|
@ -138,6 +139,8 @@ function DirectHeader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function DirectEmpty() {
|
function DirectEmpty() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavEmptyCenter>
|
<NavEmptyCenter>
|
||||||
<NavEmptyLayout
|
<NavEmptyLayout
|
||||||
|
@ -153,7 +156,7 @@ function DirectEmpty() {
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
options={
|
options={
|
||||||
<Button variant="Secondary" size="300" onClick={() => openInviteUser()}>
|
<Button variant="Secondary" size="300" onClick={() => navigate(getDirectCreatePath())}>
|
||||||
<Text size="B300" truncate>
|
<Text size="B300" truncate>
|
||||||
Direct Message
|
Direct Message
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -172,6 +175,9 @@ export function Direct() {
|
||||||
const directs = useDirectRooms();
|
const directs = useDirectRooms();
|
||||||
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
const notificationPreferences = useRoomsNotificationPreferencesContext();
|
||||||
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
const roomToUnread = useAtomValue(roomToUnreadAtom);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const createDirectSelected = useDirectCreateSelected();
|
||||||
|
|
||||||
const selectedRoomId = useSelectedRoom();
|
const selectedRoomId = useSelectedRoom();
|
||||||
const noRoomToDisplay = directs.length === 0;
|
const noRoomToDisplay = directs.length === 0;
|
||||||
|
@ -205,8 +211,8 @@ export function Direct() {
|
||||||
<PageNavContent scrollRef={scrollRef}>
|
<PageNavContent scrollRef={scrollRef}>
|
||||||
<Box direction="Column" gap="300">
|
<Box direction="Column" gap="300">
|
||||||
<NavCategory>
|
<NavCategory>
|
||||||
<NavItem variant="Background" radii="400">
|
<NavItem variant="Background" radii="400" aria-selected={createDirectSelected}>
|
||||||
<NavButton onClick={() => openInviteUser()}>
|
<NavButton onClick={() => navigate(getDirectCreatePath())}>
|
||||||
<NavItemContent>
|
<NavItemContent>
|
||||||
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
<Box as="span" grow="Yes" alignItems="Center" gap="200">
|
||||||
<Avatar size="200" radii="400">
|
<Avatar size="200" radii="400">
|
||||||
|
|
|
@ -1,33 +1,75 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { WelcomePage } from '../WelcomePage';
|
import { Box, Icon, IconButton, Icons, Scroll } from 'folds';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getDirectCreateSearchParams } from '../../pathSearchParam';
|
import { getDirectCreateSearchParams } from '../../pathSearchParam';
|
||||||
import { getDirectPath, getDirectRoomPath } from '../../pathUtils';
|
import { getDirectRoomPath } from '../../pathUtils';
|
||||||
import { getDMRoomFor } from '../../../utils/matrix';
|
import { getDMRoomFor } from '../../../utils/matrix';
|
||||||
import { openInviteUser } from '../../../../client/action/navigation';
|
|
||||||
import { useDirectRooms } from './useDirectRooms';
|
import { useDirectRooms } from './useDirectRooms';
|
||||||
|
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||||
|
import {
|
||||||
|
Page,
|
||||||
|
PageContent,
|
||||||
|
PageContentCenter,
|
||||||
|
PageHeader,
|
||||||
|
PageHero,
|
||||||
|
PageHeroSection,
|
||||||
|
} from '../../../components/page';
|
||||||
|
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
||||||
|
import { CreateChat } from '../../../features/create-chat';
|
||||||
|
|
||||||
export function DirectCreate() {
|
export function DirectCreate() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const screenSize = useScreenSizeContext();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { userId } = getDirectCreateSearchParams(searchParams);
|
const { userId } = getDirectCreateSearchParams(searchParams);
|
||||||
|
|
||||||
const directs = useDirectRooms();
|
const directs = useDirectRooms();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const room = getDMRoomFor(mx, userId);
|
const roomId = getDMRoomFor(mx, userId)?.roomId;
|
||||||
const { roomId } = room ?? {};
|
|
||||||
if (roomId && directs.includes(roomId)) {
|
if (roomId && directs.includes(roomId)) {
|
||||||
navigate(getDirectRoomPath(roomId), { replace: true });
|
navigate(getDirectRoomPath(roomId), { replace: true });
|
||||||
} else {
|
|
||||||
openInviteUser(undefined, userId);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
navigate(getDirectPath(), { replace: true });
|
|
||||||
}
|
}
|
||||||
}, [mx, navigate, directs, userId]);
|
}, [mx, navigate, directs, userId]);
|
||||||
|
|
||||||
return <WelcomePage />;
|
return (
|
||||||
|
<Page>
|
||||||
|
{screenSize === ScreenSize.Mobile && (
|
||||||
|
<PageHeader balance outlined={false}>
|
||||||
|
<Box grow="Yes" alignItems="Center" gap="200">
|
||||||
|
<BackRouteHandler>
|
||||||
|
{(onBack) => (
|
||||||
|
<IconButton onClick={onBack}>
|
||||||
|
<Icon src={Icons.ArrowLeft} />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</BackRouteHandler>
|
||||||
|
</Box>
|
||||||
|
</PageHeader>
|
||||||
|
)}
|
||||||
|
<Box grow="Yes">
|
||||||
|
<Scroll hideTrack visibility="Hover">
|
||||||
|
<PageContent>
|
||||||
|
<PageContentCenter>
|
||||||
|
<PageHeroSection>
|
||||||
|
<Box direction="Column" gap="700">
|
||||||
|
<PageHero
|
||||||
|
icon={<Icon size="600" src={Icons.Mention} />}
|
||||||
|
title="Create Chat"
|
||||||
|
subTitle="Start a private, encrypted chat by entering a user ID."
|
||||||
|
/>
|
||||||
|
<CreateChat defaultUserId={userId} />
|
||||||
|
</Box>
|
||||||
|
</PageHeroSection>
|
||||||
|
</PageContentCenter>
|
||||||
|
</PageContent>
|
||||||
|
</Scroll>
|
||||||
|
</Box>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function HomeCreateRoom() {
|
||||||
<PageHero
|
<PageHero
|
||||||
icon={<Icon size="600" src={Icons.Hash} />}
|
icon={<Icon size="600" src={Icons.Hash} />}
|
||||||
title="Create Room"
|
title="Create Room"
|
||||||
subTitle="Build a Room for Real-Time Conversations"
|
subTitle="Build a Room for Real-Time Conversations."
|
||||||
/>
|
/>
|
||||||
<CreateRoomForm onCreate={navigateRoom} />
|
<CreateRoomForm onCreate={navigateRoom} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -230,8 +230,11 @@ export const addRoomIdToMDirect = async (
|
||||||
roomId: string,
|
roomId: string,
|
||||||
userId: string
|
userId: string
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const mDirectsEvent = mx.getAccountData(AccountDataEvent.Direct);
|
const mDirectsEvent = mx.getAccountData(AccountDataEvent.Direct as any);
|
||||||
const userIdToRoomIds: Record<string, string[]> = mDirectsEvent?.getContent() ?? {};
|
let userIdToRoomIds: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
if (typeof mDirectsEvent !== 'undefined')
|
||||||
|
userIdToRoomIds = structuredClone(mDirectsEvent.getContent());
|
||||||
|
|
||||||
// remove it from the lists of any others users
|
// remove it from the lists of any others users
|
||||||
// (it can only be a DM room for one person)
|
// (it can only be a DM room for one person)
|
||||||
|
@ -252,12 +255,15 @@ export const addRoomIdToMDirect = async (
|
||||||
}
|
}
|
||||||
userIdToRoomIds[userId] = roomIds;
|
userIdToRoomIds[userId] = roomIds;
|
||||||
|
|
||||||
await mx.setAccountData(AccountDataEvent.Direct, userIdToRoomIds);
|
await mx.setAccountData(AccountDataEvent.Direct as any, userIdToRoomIds as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string): Promise<void> => {
|
export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string): Promise<void> => {
|
||||||
const mDirectsEvent = mx.getAccountData(AccountDataEvent.Direct);
|
const mDirectsEvent = mx.getAccountData(AccountDataEvent.Direct as any);
|
||||||
const userIdToRoomIds: Record<string, string[]> = mDirectsEvent?.getContent() ?? {};
|
let userIdToRoomIds: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
if (typeof mDirectsEvent !== 'undefined')
|
||||||
|
userIdToRoomIds = structuredClone(mDirectsEvent.getContent());
|
||||||
|
|
||||||
Object.keys(userIdToRoomIds).forEach((targetUserId) => {
|
Object.keys(userIdToRoomIds).forEach((targetUserId) => {
|
||||||
const roomIds = userIdToRoomIds[targetUserId];
|
const roomIds = userIdToRoomIds[targetUserId];
|
||||||
|
@ -267,7 +273,7 @@ export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string):
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await mx.setAccountData(AccountDataEvent.Direct, userIdToRoomIds);
|
await mx.setAccountData(AccountDataEvent.Direct as any, userIdToRoomIds as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mxcUrlToHttp = (
|
export const mxcUrlToHttp = (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue