mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-02-15 07:28:59 +00:00
Pinned messages UI fixes
This commit is contained in:
@@ -11,12 +11,9 @@ export default {
|
||||
} satisfies Meta<Props>;
|
||||
|
||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
direction: overrideProps.direction || 'outgoing',
|
||||
expirationLength: overrideProps.expirationLength || 30 * 1000,
|
||||
expirationTimestamp:
|
||||
overrideProps.expirationTimestamp || Date.now() + 30 * 1000,
|
||||
withImageNoCaption: overrideProps.withImageNoCaption || false,
|
||||
withSticker: overrideProps.withSticker || false,
|
||||
});
|
||||
|
||||
export const _30Seconds = (): React.JSX.Element => {
|
||||
@@ -51,38 +48,6 @@ export function Expired(): React.JSX.Element {
|
||||
return <ExpireTimer {...props} />;
|
||||
}
|
||||
|
||||
export function Sticker(): React.JSX.Element {
|
||||
const props = createProps({
|
||||
withSticker: true,
|
||||
});
|
||||
|
||||
return <ExpireTimer {...props} />;
|
||||
}
|
||||
|
||||
export function ImageNoCaption(): React.JSX.Element {
|
||||
const props = createProps({
|
||||
withImageNoCaption: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ backgroundColor: 'darkgreen' }}>
|
||||
<ExpireTimer {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Incoming(): React.JSX.Element {
|
||||
const props = createProps({
|
||||
direction: 'incoming',
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ backgroundColor: 'darkgreen' }}>
|
||||
<ExpireTimer {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ExpirationTooFarOut(): React.JSX.Element {
|
||||
const props = createProps({
|
||||
expirationTimestamp: Date.now() + 150 * 1000,
|
||||
|
||||
@@ -6,22 +6,14 @@ import classNames from 'classnames';
|
||||
|
||||
import { getIncrement, getTimerBucket } from '../../util/timer.std.js';
|
||||
|
||||
export type Props = {
|
||||
direction?: 'incoming' | 'outgoing';
|
||||
export type Props = Readonly<{
|
||||
expirationLength: number;
|
||||
expirationTimestamp?: number;
|
||||
isOutlineOnlyBubble?: boolean;
|
||||
withImageNoCaption?: boolean;
|
||||
withSticker?: boolean;
|
||||
};
|
||||
}>;
|
||||
|
||||
export function ExpireTimer({
|
||||
direction,
|
||||
expirationLength,
|
||||
expirationTimestamp,
|
||||
isOutlineOnlyBubble,
|
||||
withImageNoCaption,
|
||||
withSticker,
|
||||
}: Props): React.JSX.Element {
|
||||
const [, forceUpdate] = useReducer(() => ({}), {});
|
||||
|
||||
@@ -40,13 +32,7 @@ export function ExpireTimer({
|
||||
<div
|
||||
className={classNames(
|
||||
'module-expire-timer',
|
||||
`module-expire-timer--${bucket}`,
|
||||
direction ? `module-expire-timer--${direction}` : null,
|
||||
isOutlineOnlyBubble ? 'module-expire-timer--outline-only-bubble' : null,
|
||||
direction && withImageNoCaption
|
||||
? 'module-expire-timer--with-image-no-caption'
|
||||
: null,
|
||||
withSticker ? 'module-expire-timer--with-sticker' : null
|
||||
`module-expire-timer--${bucket}`
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -220,12 +220,8 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
|
||||
) : null}
|
||||
{expirationLength ? (
|
||||
<ExpireTimer
|
||||
direction={metadataDirection}
|
||||
isOutlineOnlyBubble={isOutlineOnlyBubble}
|
||||
expirationLength={expirationLength}
|
||||
expirationTimestamp={expirationTimestamp}
|
||||
withImageNoCaption={withImageNoCaption}
|
||||
withSticker={isSticker}
|
||||
/>
|
||||
) : null}
|
||||
{textPending ? (
|
||||
|
||||
@@ -77,7 +77,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
||||
canEditMessage: true,
|
||||
canEndPoll: false,
|
||||
canForward: true,
|
||||
canPinMessages: true,
|
||||
canPinMessage: true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
canRetry: true,
|
||||
|
||||
@@ -47,7 +47,7 @@ function mockMessageTimelineItem(
|
||||
canEditMessage: true,
|
||||
canEndPoll: false,
|
||||
canForward: true,
|
||||
canPinMessages: true,
|
||||
canPinMessage: true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
canRetry: true,
|
||||
|
||||
@@ -239,7 +239,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
canCopy: true,
|
||||
canEditMessage: true,
|
||||
canEndPoll: overrideProps.direction === 'outgoing',
|
||||
canPinMessages: overrideProps.canPinMessages ?? true,
|
||||
canPinMessage: overrideProps.canPinMessage ?? true,
|
||||
canReact: true,
|
||||
canReply: true,
|
||||
canDownload: true,
|
||||
|
||||
@@ -54,7 +54,7 @@ export type PropsData = {
|
||||
canRetryDeleteForEveryone: boolean;
|
||||
canReact: boolean;
|
||||
canReply: boolean;
|
||||
canPinMessages: boolean;
|
||||
canPinMessage: boolean;
|
||||
hasMaxPinnedMessages: boolean;
|
||||
selectedReaction?: string;
|
||||
isTargeted?: boolean;
|
||||
@@ -114,7 +114,7 @@ export function TimelineMessage(props: Props): React.JSX.Element {
|
||||
canReply,
|
||||
canRetry,
|
||||
canRetryDeleteForEveryone,
|
||||
canPinMessages,
|
||||
canPinMessage,
|
||||
containerElementRef,
|
||||
containerWidthBreakpoint,
|
||||
conversationId,
|
||||
@@ -368,11 +368,9 @@ export function TimelineMessage(props: Props): React.JSX.Element {
|
||||
});
|
||||
}}
|
||||
onPinMessage={
|
||||
canPinMessages && !isPinned ? handleOpenPinMessageDialog : null
|
||||
}
|
||||
onUnpinMessage={
|
||||
canPinMessages && isPinned ? handleUnpinMessage : null
|
||||
canPinMessage && !isPinned ? handleOpenPinMessageDialog : null
|
||||
}
|
||||
onUnpinMessage={canPinMessage && isPinned ? handleUnpinMessage : null}
|
||||
onMoreInfo={() =>
|
||||
pushPanelForConversation({
|
||||
type: PanelType.MessageDetails,
|
||||
@@ -388,7 +386,7 @@ export function TimelineMessage(props: Props): React.JSX.Element {
|
||||
canCopy,
|
||||
canEditMessage,
|
||||
canForward,
|
||||
canPinMessages,
|
||||
canPinMessage,
|
||||
canRetry,
|
||||
canSelect,
|
||||
canEndPoll,
|
||||
|
||||
@@ -182,10 +182,28 @@ export function Variants(): React.JSX.Element {
|
||||
title="Poll"
|
||||
message={{ poll: { question: `${SHORT_TEXT}?` } }}
|
||||
/>
|
||||
<Variant title="Sticker" message={{ sticker: true }} />
|
||||
<Variant title="Contact" message={{ contact: { name: 'Tyler' } }} />
|
||||
<Variant
|
||||
title="Sticker"
|
||||
message={{
|
||||
sticker: true,
|
||||
attachment: { type: 'image', url: IMAGE_URL },
|
||||
}}
|
||||
/>
|
||||
<Variant
|
||||
title="Contact"
|
||||
message={{
|
||||
contact: { name: 'Tyler' },
|
||||
attachment: { type: 'file', name: '' },
|
||||
}}
|
||||
/>
|
||||
<Variant title="Payment" message={{ payment: true }} />
|
||||
<Variant title="View-Once Media" message={{ viewOnceMedia: true }} />
|
||||
<Variant
|
||||
title="View-Once Media"
|
||||
message={{
|
||||
viewOnceMedia: true,
|
||||
attachment: { type: 'image', url: IMAGE_URL },
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ function TabsList(props: {
|
||||
return (
|
||||
<AriaClickable.SubWidget>
|
||||
<Tabs.List className={tw('flex h-full flex-col')}>
|
||||
{props.pins.toReversed().map((pin, pinIndex) => {
|
||||
{props.pins.map((pin, pinIndex) => {
|
||||
return (
|
||||
<TabTrigger
|
||||
key={pin.id}
|
||||
@@ -395,7 +395,39 @@ function getMessagePreview(i18n: LocalizerType, message: PinMessage): Preview {
|
||||
text ??= <MessageTextPreview i18n={i18n} text={message.text} />;
|
||||
}
|
||||
|
||||
// 3. And everything else...
|
||||
// 3. Check specific types of messages (before checking attachments)
|
||||
if (message.payment) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Payment');
|
||||
icon ??= {
|
||||
symbol: 'creditcard',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Payment'
|
||||
),
|
||||
};
|
||||
} else if (message.poll != null) {
|
||||
text ??= <UserText text={message.poll.question} />;
|
||||
icon ??= {
|
||||
symbol: 'poll',
|
||||
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Poll'),
|
||||
};
|
||||
} else if (message.sticker) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Sticker');
|
||||
icon ??= {
|
||||
symbol: 'sticker',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Sticker'
|
||||
),
|
||||
};
|
||||
} else if (message.contact != null) {
|
||||
const name = message.contact.name ?? i18n('icu:unknownContact');
|
||||
text ??= <UserText text={name} />;
|
||||
icon ??= {
|
||||
symbol: 'person-circle',
|
||||
label: name,
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Check attachments (make sure to check types like sticker/contact first)
|
||||
if (message.attachment != null) {
|
||||
if (message.attachment.type === 'image') {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Photo');
|
||||
@@ -426,35 +458,6 @@ function getMessagePreview(i18n: LocalizerType, message: PinMessage): Preview {
|
||||
} else {
|
||||
throw missingCaseError(message.attachment);
|
||||
}
|
||||
} else if (message.contact != null) {
|
||||
const name = message.contact.name ?? i18n('icu:unknownContact');
|
||||
text ??= <UserText text={name} />;
|
||||
icon ??= {
|
||||
symbol: 'person-circle',
|
||||
label: name,
|
||||
};
|
||||
} else if (message.payment) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Payment');
|
||||
icon ??= {
|
||||
symbol: 'creditcard',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Payment'
|
||||
),
|
||||
};
|
||||
} else if (message.poll != null) {
|
||||
text ??= <UserText text={message.poll.question} />;
|
||||
icon ??= {
|
||||
symbol: 'poll',
|
||||
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Poll'),
|
||||
};
|
||||
} else if (message.sticker) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Sticker');
|
||||
icon ??= {
|
||||
symbol: 'sticker',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Sticker'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return { icon, text };
|
||||
|
||||
Reference in New Issue
Block a user