Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion ts/components/buttons/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const StyledMenuButton = styled.button`
width: 51px;
height: 33px;
cursor: pointer;
flex-shrink: 0;

transition: var(--default-duration);

Expand Down Expand Up @@ -48,17 +49,24 @@ export function useNewConversationCallback() {
};
}

const useLocalLeftOverlayModeType = useLeftOverlayModeType;

/**
* This is the Session Menu Button. i.e. the button on top of the conversation list to start a new conversation.
* It has two state: selected or not and so we use an checkbox input to keep the state in sync.
*/
export const MenuButton = () => {
const onClick = useNewConversationCallback();
const leftOverlayMode = useLocalLeftOverlayModeType();

if (leftOverlayMode) {
return null;
}

return (
<StyledMenuButton data-testid="new-conversation-button" onClick={onClick}>
<LucideIcon
unicode={LUCIDE_ICONS_UNICODE.PLUS}
unicode={leftOverlayMode ? LUCIDE_ICONS_UNICODE.X : LUCIDE_ICONS_UNICODE.PLUS}
iconSize="large"
aria-label={tr('contentDescriptionChooseConversationType')}
/>
Expand Down
12 changes: 6 additions & 6 deletions ts/components/buttons/avatar/AvatarExitQrCodeButton.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { focusVisibleBoxShadowOutsetStr } from '../../../styles/focusVisible';
import { LUCIDE_ICONS_UNICODE } from '../../icon/lucide';
import { SessionLucideIconButton } from '../../icon/SessionIconButton';
import { SessionIconButton } from '../../icon/SessionIconButton';

export function AvatarExitQrCodeButton({ onExitQrCodeView }: { onExitQrCodeView: () => void }) {
return (
<SessionLucideIconButton
unicode={LUCIDE_ICONS_UNICODE.USER_ROUND}
iconSize={'large'}
<SessionIconButton
iconType="userRoundFilled"
iconSize={'huge'}
backgroundColor="var(--primary-color)"
iconColor="var(--black-color)"
padding="var(--margins-xs)"
Expand All @@ -18,8 +17,9 @@ export function AvatarExitQrCodeButton({ onExitQrCodeView }: { onExitQrCodeView:
insetInlineEnd: '-15px',
display: 'flex',
alignItems: 'center',
borderRadius: '50%',
}}
borderRadius="50%"
iconPadding="var(--margins-xs)"
/>
);
}
67 changes: 60 additions & 7 deletions ts/components/conversation/media-gallery/MediaGridItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { LUCIDE_ICONS_UNICODE } from '../../icon/lucide';
import { createButtonOnKeyDownForClickEventHandler } from '../../../util/keyboardShortcuts';
import { getAppDispatch } from '../../../state/dispatch';
import { focusVisibleBoxShadowOutset } from '../../../styles/focusVisible';
import { Menu, MenuItem } from '../../menu/items/MenuItem';
import { SessionContextMenuContainer } from '../../SessionContextMenuContainer';
import { tr } from '../../../localization';
import { saveAttachmentToDisk } from '../../../util/attachment/attachmentsUtil';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { showContextMenu } from '../../../util/contextMenu';

type Props = {
mediaItem: MediaItemType;
Expand Down Expand Up @@ -40,8 +46,7 @@ const StyledMediaGridItemImageContainer = styled.div`
position: relative;
`;

const MediaGridItemContent = (props: Props) => {
const { mediaItem } = props;
const MediaGridItemContent = ({ mediaItem }: Pick<Props, 'mediaItem'>) => {
const { attachment, contentType } = mediaItem;

const urlToDecrypt = mediaItem.thumbnailObjectUrl || '';
Expand Down Expand Up @@ -109,20 +114,68 @@ const MediaGridItemContent = (props: Props) => {
return <LucideIcon iconSize="small" unicode={LUCIDE_ICONS_UNICODE.FILE} />;
};

export const MediaGridItem = (props: Props) => {
export const MediaGridItem = ({ mediaItem, mediaItems }: Props) => {
const dispatch = getAppDispatch();
const msgId = mediaItem.messageId;
const contextMenuId = `media-grid-item-menu-${msgId}`;

const onClick = () => {
const lightBoxOptions: LightBoxOptions = {
media: props.mediaItems,
attachment: props.mediaItem.attachment,
media: mediaItems,
attachment: mediaItem.attachment,
};

dispatch(updateLightBoxOptions(lightBoxOptions));
};
const onKeyDown = createButtonOnKeyDownForClickEventHandler(onClick);

return (
<StyledMediaGridItem role="button" onClick={onClick} tabIndex={0} onKeyDown={onKeyDown}>
<MediaGridItemContent {...props} />
<StyledMediaGridItem
role="button"
onClick={onClick}
tabIndex={0}
onKeyDown={onKeyDown}
onContextMenu={e => {
showContextMenu({ event: e, id: contextMenuId });
}}
>
<MediaGridItemContent mediaItem={mediaItem} />
<GridItemContextMenu contextMenuId={contextMenuId} mediaItem={mediaItem} />
</StyledMediaGridItem>
);
};

function GridItemContextMenu({
contextMenuId,
mediaItem,
}: {
contextMenuId: string;
mediaItem: MediaItemType;
}) {
const selectedConversationKey = useSelectedConversationKey();
function onSave() {
if (selectedConversationKey) {
void saveAttachmentToDisk({
attachment: mediaItem.attachment,
index: mediaItem.index,
messageSender: mediaItem.messageSender,
messageTimestamp: mediaItem.messageTimestamp,
conversationId: selectedConversationKey,
});
}
}

return (
<SessionContextMenuContainer>
<Menu id={contextMenuId}>
<MenuItem
iconType={LUCIDE_ICONS_UNICODE.ARROW_DOWN_TO_LINE}
isDangerAction={false}
onClick={onSave}
>
{tr('save')}
</MenuItem>
</Menu>
</SessionContextMenuContainer>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const MessageContent = (props: Props) => {
role="button"
title={toolTipTitle}
$msgDirection={direction}
data-testid="message-content-inner"
>
{hideAvatar ? null : (
<StyledAvatarContainer>
Expand All @@ -171,6 +172,9 @@ export const MessageContent = (props: Props) => {
flexDirection: 'column',
gap: 'var(--margins-xs)',
maxWidth: '100%',
// this is needed to make sure a message with a short body and attachments
// is not forced to take the attachments width
alignItems: direction === 'incoming' ? 'flex-start' : 'flex-end',
}}
>
<IsMessageVisibleContext.Provider value={isMessageVisible}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const StyledQuote = styled.div<{
display: flex;
flex-direction: row;
align-items: stretch;
margin: var(--margins-xs);
margin-block: var(--margins-xs);
// margin-inline-start is 0 so that a body & start of quote are aligned
margin-inline: 0 var(--margins-xs);
${props => !props.$hasAttachment && 'border-left: 4px solid;'}
border-color: ${props =>
props.$isIncoming
Expand Down
9 changes: 8 additions & 1 deletion ts/components/icon/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export type SessionIconType =
| 'timer50' // no lucide alternative yet
| 'timer55' // no lucide alternative yet
| 'timer60' // no lucide alternative yet
| 'timerFixed'; // no lucide alternative yet
| 'timerFixed' // no lucide alternative yet
| 'userRoundFilled'; // no lucide alternative

export type SessionIconSize = 'small' | 'medium' | 'large' | 'huge' | 'huge2' | 'max';

Expand Down Expand Up @@ -221,4 +222,10 @@ export const icons: Record<SessionIconType, IconProps> = {
viewBox: '0 0 12 12',
ratio: 1,
},

userRoundFilled: {
path: 'M4.95 6.6a4.95 4.95 0 1 1 9.9 0 4.95 4.95 0 0 1-9.9 0M2.475 17.384a6.893 6.893 0 0 1 6.893-6.892h1.064a6.893 6.893 0 0 1 6.893 6.892.766.766 0 0 1-.766.766H3.241a.766.766 0 0 1-.766-.766',
viewBox: '0 0 20 20',
ratio: 1,
},
};
70 changes: 63 additions & 7 deletions ts/components/leftpane/LeftPaneSectionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { getAppDispatch } from '../../state/dispatch';
import { LeftOverlayType, sectionActions } from '../../state/ducks/section';
import { useLeftOverlayModeType } from '../../state/selectors/section';
Expand All @@ -23,15 +24,40 @@ import { tr } from '../../localization/localeTools';
import { userSettingsModal } from '../../state/ducks/modalDialog';
import { SettingsKey } from '../../data/settings-key';
import { LeftPaneAnnouncements } from './LeftPaneAnnoucements';
import { useAppIsFocused } from '../../hooks/useAppFocused';

const StyledLeftPaneSectionHeader = styled(Flex)`
height: var(--main-view-header-height);
padding-inline-end: 7px;
transition: var(--default-duration);
`;

const SectionTitle = styled(H4)`
const SectionTitleWrapper = styled.div`
flex-grow: 1;
overflow: hidden;
min-width: 0;
flex-shrink: 100;
margin-inline-end: var(--margins-sm);
`;

const SectionTitle = styled(H4)<{ $overflowPx?: number; $appIsFocused: boolean }>`
white-space: nowrap;
${({ $overflowPx, $appIsFocused }) =>
$overflowPx && $appIsFocused
? css`
animation: sectionTitleMarquee 6s ease-in-out infinite;
@keyframes sectionTitleMarquee {
0%,
20% {
transform: translateX(0);
}
80%,
100% {
transform: translateX(-${$overflowPx}px);
}
}
`
: ''};
`;

const StyledProgressBarContainer = styled.div`
Expand Down Expand Up @@ -167,6 +193,8 @@ export const LeftPaneSectionHeader = () => {
const noOverlayMode = !leftOverlayMode;
const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt);

const isAppFocused = useAppIsFocused();

const dispatch = getAppDispatch();
const goBack = () => {
if (!leftOverlayMode) {
Expand All @@ -193,6 +221,27 @@ export const LeftPaneSectionHeader = () => {

const label = getLeftPaneHeaderLabel(leftOverlayMode);

const wrapperRef = useRef<HTMLDivElement>(null);
const [overflowPx, setOverflowPx] = useState<number | undefined>(undefined);

useEffect(() => {
const container = wrapperRef.current;
if (!container) {
return;
}
const check = () => {
const diff = container.scrollWidth - container.clientWidth;
setOverflowPx(diff > 0 ? diff : undefined);
};
check();
const ro = new ResizeObserver(check);
ro.observe(container);
// eslint-disable-next-line consistent-return
return () => {
ro.disconnect();
};
}, [label]);

return (
<Flex $flexDirection="column">
<StyledLeftPaneSectionHeader
Expand All @@ -208,15 +257,22 @@ export const LeftPaneSectionHeader = () => {
unicode={LUCIDE_ICONS_UNICODE.CHEVRON_LEFT}
onClick={goBack}
dataTestId="back-button"
padding="var(--margins-sm)"
padding="var(--margins-xs)"
/>
) : (
<SpacerSM />
)}
<SectionTitle color={'var(--text-primary-color)'} onClick={goBack}>
{label}
</SectionTitle>
{!leftOverlayMode && <MenuButton />}
<SectionTitleWrapper ref={wrapperRef}>
<SectionTitle
color={'var(--text-primary-color)'}
onClick={goBack}
$overflowPx={overflowPx}
$appIsFocused={isAppFocused}
>
{label}
</SectionTitle>
</SectionTitleWrapper>
<MenuButton />
</StyledLeftPaneSectionHeader>
{noOverlayMode && showRecoveryPhrasePrompt ? <LeftPaneBanner /> : null}
{noOverlayMode ? <LeftPaneAnnouncements /> : null}
Expand Down
Loading
Loading