Skip to content
37 changes: 35 additions & 2 deletions src/app/features/room/RoomInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@
import { useRoomCreators } from '$hooks/useRoomCreators';
import { useRoomPermissions } from '$hooks/useRoomPermissions';
import { AutocompleteNotice } from '$components/editor/autocomplete/AutocompleteNotice';
import {
getCurrentlyUsedPerMessageProfileForRoom,
PerMessageProfile,
} from '$hooks/usePerMessageProfile';
import { getSupportedAudioExtension } from '$plugins/voice-recorder-kit/supportedCodec';
import { SchedulePickerDialog } from './schedule-send';
import * as css from './schedule-send/SchedulePickerDialog.css';
Expand Down Expand Up @@ -270,6 +274,20 @@
room
);

// Add state to cache the profile
const [perMessageProfile, setPerMessageProfile] = useState<PerMessageProfile | null>(null);

// Fetch and cache the profile when the roomId changes
useEffect(() => {
const fetchPmP = async () => {
const profile = await getCurrentlyUsedPerMessageProfileForRoom(mx, roomId);
console.debug('Fetched per-message profile:', { profile, roomId });

Check warning on line 284 in src/app/features/room/RoomInput.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
setPerMessageProfile(profile ?? null); // Convert undefined to null
};

fetchPmP();
}, [mx, roomId]);

const [uploadBoard, setUploadBoard] = useState(true);
const [selectedFiles, setSelectedFiles] = useAtom(roomIdToUploadItemsAtomFamily(draftKey));
const uploadFamilyObserverAtom = createUploadFamilyObserverAtom(
Expand Down Expand Up @@ -630,6 +648,20 @@
body,
};

if (perMessageProfile) {
console.warn(

Check warning on line 652 in src/app/features/room/RoomInput.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
'Using per-message profile:',
perMessageProfile.id,
perMessageProfile.name,
perMessageProfile.avatarUrl
);
content['com.beeper.per_message_profile'] = {
id: perMessageProfile.id,
displayname: perMessageProfile.name,
avatar_url: perMessageProfile.avatarUrl,
};
}

if (replyDraft && !silentReply) {
mentionData.users.add(replyDraft.userId);
}
Expand Down Expand Up @@ -724,20 +756,21 @@
canSendReaction,
mx,
roomId,
threadRootId,
perMessageProfile,
replyDraft,
silentReply,
scheduledTime,
editingScheduledDelayId,
handleQuickReact,
commands,
sendTypingStatus,
room,
queryClient,
threadRootId,
setReplyDraft,
isEncrypted,
setEditingScheduledDelayId,
setScheduledTime,
room,
]);

const handleKeyDown: KeyboardEventHandler = useCallback(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { SequenceCard } from '$components/sequence-card';
import { Box, Button, Text, Avatar, config, Icon, IconButton, Icons, Input } from 'folds';
// Try relative import for CompactUploadCardRenderer
import { MatrixClient } from 'matrix-js-sdk';
import { useCallback, useMemo, useState } from 'react';
import { mxcUrlToHttp } from '$utils/matrix';
import { useFilePicker } from '$hooks/useFilePicker';
import { useMediaAuthentication } from '$hooks/useMediaAuthentication';
import { useObjectURL } from '$hooks/useObjectURL';
import { createUploadAtom } from '$state/upload';
import { UserAvatar } from '$components/user-avatar';
import { CompactUploadCardRenderer } from '$components/upload-card';
import { SequenceCardStyle } from '../styles.css';

/**
* the props we use for the per-message profile editor, which is used to edit a per-message profile. This is used in the settings page when the user wants to edit a profile.
*/
type PerMessageProfileEditorProps = {
mx: MatrixClient;
profileId: string;
avatarMxcUrl?: string;
displayName?: string;
onChange?: (profile: { id: string; name: string; avatarUrl?: string }) => void;
};

export function PerMessageProfileEditor({
mx,
profileId,
avatarMxcUrl,
displayName,
onChange,
}: Readonly<PerMessageProfileEditorProps>) {
const useAuthentication = useMediaAuthentication();
const [newDisplayName, setNewDisplayName] = useState(displayName ?? '');
const [imageFile, setImageFile] = useState<File | undefined>();
const imageFileURL = useObjectURL(imageFile);
const avatarUrl = useMemo(() => {
if (imageFileURL) return imageFileURL;
if (avatarMxcUrl) {
return mxcUrlToHttp(mx, avatarMxcUrl, useAuthentication, 96, 96, 'crop') ?? undefined;
}
return undefined;
}, [imageFileURL, avatarMxcUrl, mx, useAuthentication]);
const uploadAtom = useMemo(() => {
if (imageFile) return createUploadAtom(imageFile);
return undefined;
}, [imageFile]);
const pickFile = useFilePicker(setImageFile, false);
const handleRemoveUpload = useCallback(() => {
setImageFile(undefined);
}, []);
const handleUploaded = useCallback(
(upload: { status: string; mxc: string }) => {
if (upload && upload.status === 'success' && onChange) {
onChange({ id: profileId, name: newDisplayName, avatarUrl: upload.mxc });
}
setImageFile(undefined);
},
[onChange, profileId, newDisplayName]
);
const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setNewDisplayName(e.target.value);
}, []);

// Added missing state and logic for display name editing
const [changingDisplayName, setChangingDisplayName] = useState(false);
const [disableSetDisplayname, setDisableSetDisplayname] = useState(false);

// Determine if there are changes to the display name or avatar
const hasChanges = useMemo(
() => newDisplayName !== (displayName ?? '') || !!imageFile,
[newDisplayName, displayName, imageFile]
);

// Reset handler for display name
const handleReset = useCallback(() => {
setNewDisplayName(displayName ?? '');
setChangingDisplayName(false);
setDisableSetDisplayname(false);
}, [displayName]);

return (
<Box direction="Row" gap="200" grow="Yes" style={{ width: '100%', minWidth: 500 }}>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="500"
style={{
width: '100%',
minHeight: 180,
margin: '0 auto',
padding: 32,
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'Center',
}}
>
<Box direction="Row" grow="Yes" gap="400" alignItems="Center" style={{ width: '100%' }}>
<Box
direction="Column"
alignItems="Center"
gap="100"
style={{ minWidth: 96, maxWidth: 120, flexShrink: 0, alignSelf: 'flex-start' }}
>
<Box
style={{
display: 'flex',
justifyContent: 'Center',
alignItems: 'Center',
width: 50,
height: 50,
}}
>
<Avatar size="500" radii="300">
<UserAvatar
userId={profileId}
src={avatarUrl}
renderFallback={() => <Text size="H4">p</Text>}
/>
</Avatar>
</Box>
<Button
onClick={() => pickFile('image/*')}
size="300"
variant="Secondary"
fill="Soft"
outlined
radii="300"
style={{ width: 96, marginTop: 8, boxSizing: 'border-box', alignSelf: 'Center' }}
>
<Text size="B300">Upload</Text>
</Button>
</Box>
<Box
direction="Column"
gap="200"
style={{ flex: 1, width: '100%', justifyContent: 'Center' }}
>
{uploadAtom ? (
<Box gap="200" direction="Column">
<CompactUploadCardRenderer
uploadAtom={uploadAtom}
onRemove={handleRemoveUpload}
onComplete={handleUploaded}
/>
</Box>
) : (
<Box direction="Row" alignItems="Center" style={{ width: '100%' }}>
<Input
required
name="displayNameInput"
value={newDisplayName}
onChange={handleNameChange}
variant="Secondary"
radii="300"
style={{
flex: 1,
minWidth: 0,
width: '100%',
maxWidth: 320,
paddingRight: config.space.S200,
fontSize: 18,
height: 44,
}}
placeholder="Display name"
readOnly={changingDisplayName || disableSetDisplayname}
after={
hasChanges &&
!changingDisplayName && (
<IconButton
type="reset"
onClick={handleReset}
size="300"
radii="300"
variant="Secondary"
>
<Icon src={Icons.Cross} size="100" />
</IconButton>
)
}
/>
</Box>
)}
</Box>
</Box>
</SequenceCard>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMatrixClient } from '$hooks/useMatrixClient';
import { getAllPerMessageProfiles, PerMessageProfile } from '$hooks/usePerMessageProfile';
import { useEffect, useState } from 'react';
import { Box } from 'folds';
import { PerMessageProfileEditor } from './PerMessageProfileEditor';

/**
* Renders a list of per-message profiles along with an editor.
* @returns rendering of per message profile list including editor
*/
export function PerMessageProfileOverview() {
const mx = useMatrixClient();
const [profiles, setProfiles] = useState<PerMessageProfile[]>([]);

useEffect(() => {
const fetchProfiles = async () => {
const fetchedProfiles = await getAllPerMessageProfiles(mx);
setProfiles(fetchedProfiles);
};
fetchProfiles();
}, [mx]);

return (
<Box gap="200" direction="Column">
{profiles.map((profile) => (
<PerMessageProfileEditor
mx={mx}
profileId={profile.id}
avatarMxcUrl={profile.avatarUrl}
displayName={profile.name}
/>
))}
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Page, PageHeader } from '$components/page';
import { Box, IconButton, Icon, Icons, Text } from 'folds';
import { PerMessageProfileOverview } from './PerMessageProfileOverview';

type PerMessageProfilePageProps = {
requestClose: () => void;
};

export function PerMessageProfilePage({ requestClose }: PerMessageProfilePageProps) {
return (
<Page>
<PageHeader outlined={false}>
<Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate>
Per Message Profiles
</Text>
</Box>
<Box shrink="No">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>
</Box>
</PageHeader>
<Box grow="Yes">
<PerMessageProfileOverview />
</Box>
</Page>
);
}
10 changes: 10 additions & 0 deletions src/app/features/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ import { General } from './general';
import { Cosmetics } from './cosmetics/Cosmetics';
import { Experimental } from './experimental/Experimental';
import { KeyboardShortcuts } from './keyboard-shortcuts';
import { PerMessageProfilePage } from './PerMessageProfiles/PerMessageProfilesPage';

export enum SettingsPages {
GeneralPage,
AccountPage,
PerMessageProfilesPage, // Renamed to avoid conflict
NotificationPage,
DevicesPage,
EmojisStickersPage,
Expand Down Expand Up @@ -70,6 +72,11 @@ const useSettingsMenuItems = (): SettingsMenuItem[] =>
name: 'Account',
icon: Icons.User,
},
{
page: SettingsPages.PerMessageProfilesPage,
name: 'Per-Message Profiles',
icon: Icons.User,
},
{
page: SettingsPages.CosmeticsPage,
name: 'Appearance',
Expand Down Expand Up @@ -247,6 +254,9 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
{activePage === SettingsPages.AccountPage && (
<Account requestClose={handlePageRequestClose} />
)}
{activePage === SettingsPages.PerMessageProfilesPage && (
<PerMessageProfilePage requestClose={handlePageRequestClose} />
)}
{activePage === SettingsPages.CosmeticsPage && (
<Cosmetics requestClose={handlePageRequestClose} />
)}
Expand Down
Loading
Loading