Phase 2+3: Chat backgrounds and per-message profiles settings
This commit is contained in:
@@ -436,6 +436,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
|
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
|
||||||
|
const [perMessageProfiles] = useSetting(settingsAtom, 'perMessageProfiles');
|
||||||
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
||||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||||
const direct = useIsDirectRoom();
|
const direct = useIsDirectRoom();
|
||||||
@@ -1649,6 +1650,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
|||||||
}
|
}
|
||||||
|
|
||||||
const collapsed =
|
const collapsed =
|
||||||
|
!perMessageProfiles &&
|
||||||
isPrevRendered &&
|
isPrevRendered &&
|
||||||
!dayDivider &&
|
!dayDivider &&
|
||||||
(!newDivider || eventSender === mx.getUserId()) &&
|
(!newDivider || eventSender === mx.getUserId()) &&
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { RoomTombstone } from './RoomTombstone';
|
|||||||
import { RoomInput } from './RoomInput';
|
import { RoomInput } from './RoomInput';
|
||||||
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
|
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
|
||||||
import { Page } from '../../components/page';
|
import { Page } from '../../components/page';
|
||||||
|
import { useSetting } from '../../state/hooks/settings';
|
||||||
|
import { settingsAtom, ChatBackground } from '../../state/settings';
|
||||||
import { useKeyDown } from '../../hooks/useKeyDown';
|
import { useKeyDown } from '../../hooks/useKeyDown';
|
||||||
import { editableActiveElement } from '../../utils/dom';
|
import { editableActiveElement } from '../../utils/dom';
|
||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
@@ -54,9 +56,31 @@ const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const CHAT_BG: Record<ChatBackground, React.CSSProperties> = {
|
||||||
|
none: {},
|
||||||
|
dots: {
|
||||||
|
backgroundImage: 'radial-gradient(circle, rgba(152,0,0,0.18) 1px, transparent 1px)',
|
||||||
|
backgroundSize: '22px 22px',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
backgroundImage:
|
||||||
|
'linear-gradient(rgba(152,0,0,0.09) 1px, transparent 1px),' +
|
||||||
|
'linear-gradient(90deg, rgba(152,0,0,0.09) 1px, transparent 1px)',
|
||||||
|
backgroundSize: '28px 28px',
|
||||||
|
},
|
||||||
|
diagonal: {
|
||||||
|
backgroundImage:
|
||||||
|
'repeating-linear-gradient(45deg, rgba(152,0,0,0.07) 0px, rgba(152,0,0,0.07) 1px, transparent 1px, transparent 16px)',
|
||||||
|
},
|
||||||
|
'solid-navy': { backgroundColor: '#0a0e1a' },
|
||||||
|
'solid-void': { backgroundColor: '#070709' },
|
||||||
|
};
|
||||||
|
|
||||||
export function RoomView({ eventId }: { eventId?: string }) {
|
export function RoomView({ eventId }: { eventId?: string }) {
|
||||||
const roomInputRef = useRef<HTMLDivElement>(null);
|
const roomInputRef = useRef<HTMLDivElement>(null);
|
||||||
const roomViewRef = useRef<HTMLDivElement>(null);
|
const roomViewRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [chatBackground] = useSetting(settingsAtom, 'chatBackground');
|
||||||
|
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
|
|
||||||
@@ -91,7 +115,7 @@ export function RoomView({ eventId }: { eventId?: string }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page ref={roomViewRef}>
|
<Page ref={roomViewRef} style={CHAT_BG[chatBackground]}>
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
<RoomTimeline
|
<RoomTimeline
|
||||||
key={roomId}
|
key={roomId}
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ function Appearance() {
|
|||||||
const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme');
|
const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme');
|
||||||
const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode');
|
const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode');
|
||||||
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
||||||
|
const [perMessageProfiles, setPerMessageProfiles] = useSetting(settingsAtom, 'perMessageProfiles');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
@@ -350,6 +351,22 @@ function Appearance() {
|
|||||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
<SettingTile title="Page Zoom" after={<PageZoomInput />} />
|
<SettingTile title="Page Zoom" after={<PageZoomInput />} />
|
||||||
</SequenceCard>
|
</SequenceCard>
|
||||||
|
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SettingTile
|
||||||
|
title="Chat Background"
|
||||||
|
description="Pattern applied behind the message timeline."
|
||||||
|
after={<SelectChatBackground />}
|
||||||
|
/>
|
||||||
|
</SequenceCard>
|
||||||
|
|
||||||
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
|
<SettingTile
|
||||||
|
title="Show Profile on Every Message"
|
||||||
|
description="Display avatar and name on each message instead of grouping consecutive messages."
|
||||||
|
after={<Switch variant="Primary" value={perMessageProfiles} onChange={setPerMessageProfiles} />}
|
||||||
|
/>
|
||||||
|
</SequenceCard>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -741,6 +758,84 @@ function Editor() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const BG_OPTIONS: { value: ChatBackground; label: string }[] = [
|
||||||
|
{ value: 'none', label: 'None' },
|
||||||
|
{ value: 'dots', label: 'Dots' },
|
||||||
|
{ value: 'grid', label: 'Grid' },
|
||||||
|
{ value: 'diagonal', label: 'Diagonal' },
|
||||||
|
{ value: 'solid-navy', label: 'Navy' },
|
||||||
|
{ value: 'solid-void', label: 'Void' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function SelectChatBackground() {
|
||||||
|
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||||
|
const [chatBackground, setChatBackground] = useSetting(settingsAtom, 'chatBackground');
|
||||||
|
|
||||||
|
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||||
|
setMenuCords(evt.currentTarget.getBoundingClientRect());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (value: ChatBackground) => {
|
||||||
|
setChatBackground(value);
|
||||||
|
setMenuCords(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size="300"
|
||||||
|
variant="Secondary"
|
||||||
|
outlined
|
||||||
|
fill="Soft"
|
||||||
|
radii="300"
|
||||||
|
after={<Icon size="300" src={Icons.ChevronBottom} />}
|
||||||
|
onClick={handleMenu}
|
||||||
|
>
|
||||||
|
<Text size="T300">
|
||||||
|
{BG_OPTIONS.find((o) => o.value === chatBackground)?.label ?? 'None'}
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
<PopOut
|
||||||
|
anchor={menuCords}
|
||||||
|
offset={5}
|
||||||
|
position="Bottom"
|
||||||
|
align="End"
|
||||||
|
content={
|
||||||
|
<FocusTrap
|
||||||
|
focusTrapOptions={{
|
||||||
|
initialFocus: false,
|
||||||
|
onDeactivate: () => setMenuCords(undefined),
|
||||||
|
clickOutsideDeactivates: true,
|
||||||
|
isKeyForward: (evt: KeyboardEvent) =>
|
||||||
|
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
|
||||||
|
isKeyBackward: (evt: KeyboardEvent) =>
|
||||||
|
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
|
||||||
|
escapeDeactivates: stopPropagation,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Menu>
|
||||||
|
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||||
|
{BG_OPTIONS.map((opt) => (
|
||||||
|
<MenuItem
|
||||||
|
key={opt.value}
|
||||||
|
size="300"
|
||||||
|
variant={chatBackground === opt.value ? 'Primary' : 'Surface'}
|
||||||
|
radii="300"
|
||||||
|
onClick={() => handleSelect(opt.value)}
|
||||||
|
>
|
||||||
|
<Text size="T300">{opt.label}</Text>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Menu>
|
||||||
|
</FocusTrap>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SelectMessageLayout() {
|
function SelectMessageLayout() {
|
||||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||||
const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout');
|
const [messageLayout, setMessageLayout] = useSetting(settingsAtom, 'messageLayout');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { atom } from 'jotai';
|
|||||||
const STORAGE_KEY = 'settings';
|
const STORAGE_KEY = 'settings';
|
||||||
export type DateFormat = 'D MMM YYYY' | 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD' | '';
|
export type DateFormat = 'D MMM YYYY' | 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD' | '';
|
||||||
export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500';
|
export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500';
|
||||||
|
export type ChatBackground = 'none' | 'dots' | 'grid' | 'diagonal' | 'solid-navy' | 'solid-void';
|
||||||
export enum MessageLayout {
|
export enum MessageLayout {
|
||||||
Modern = 0,
|
Modern = 0,
|
||||||
Compact = 1,
|
Compact = 1,
|
||||||
@@ -41,6 +42,9 @@ export interface Settings {
|
|||||||
dateFormatString: string;
|
dateFormatString: string;
|
||||||
|
|
||||||
developerTools: boolean;
|
developerTools: boolean;
|
||||||
|
|
||||||
|
chatBackground: ChatBackground;
|
||||||
|
perMessageProfiles: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSettings: Settings = {
|
const defaultSettings: Settings = {
|
||||||
@@ -75,6 +79,9 @@ const defaultSettings: Settings = {
|
|||||||
dateFormatString: 'D MMM YYYY',
|
dateFormatString: 'D MMM YYYY',
|
||||||
|
|
||||||
developerTools: false,
|
developerTools: false,
|
||||||
|
|
||||||
|
chatBackground: 'none',
|
||||||
|
perMessageProfiles: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSettings = () => {
|
export const getSettings = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user