Full width/height stories
This commit is contained in:
parent
3a1df01c9e
commit
4602cef6da
6 changed files with 163 additions and 88 deletions
|
@ -3,7 +3,6 @@
|
|||
|
||||
.StoryImage {
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
.StoryViewer {
|
||||
&__overlay {
|
||||
background: $color-gray-95;
|
||||
filter: blur(160px);
|
||||
background-size: contain;
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
|
@ -15,6 +14,8 @@
|
|||
|
||||
&__content {
|
||||
align-items: center;
|
||||
backdrop-filter: blur(90px);
|
||||
background: $color-black-alpha-20;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
|
@ -29,16 +30,19 @@
|
|||
&__close-button {
|
||||
@include button-reset;
|
||||
@include modal-close-button;
|
||||
right: 28px;
|
||||
top: var(--title-bar-drag-area-height);
|
||||
z-index: $z-index-above-base;
|
||||
}
|
||||
|
||||
&__more {
|
||||
@include button-reset;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
right: 48px;
|
||||
right: 80px;
|
||||
top: var(--title-bar-drag-area-height);
|
||||
width: 24px;
|
||||
z-index: $z-index-above-base;
|
||||
|
||||
@include color-svg('../images/icons/v2/more-horiz-24.svg', $color-white);
|
||||
|
||||
|
@ -51,14 +55,12 @@
|
|||
|
||||
&__container {
|
||||
flex-grow: 1;
|
||||
margin-top: 36px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: $z-index-base;
|
||||
}
|
||||
|
||||
&__story {
|
||||
border-radius: 12px;
|
||||
max-height: 100%;
|
||||
outline: none;
|
||||
width: auto;
|
||||
|
@ -67,7 +69,7 @@
|
|||
&__meta {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
padding: 16px;
|
||||
padding: 0 16px;
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
width: 284px;
|
||||
|
@ -94,8 +96,10 @@
|
|||
@include font-body-1-bold;
|
||||
color: $color-gray-05;
|
||||
padding: 4px 0;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&__overlay {
|
||||
@include button-reset;
|
||||
background: $color-black-alpha-60;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
|
@ -107,11 +111,9 @@
|
|||
}
|
||||
|
||||
&__actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 32px;
|
||||
min-height: 52px;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
&__reply {
|
||||
|
|
|
@ -122,3 +122,38 @@ story.add('So many stories', () => {
|
|||
/>
|
||||
);
|
||||
});
|
||||
|
||||
story.add('Caption', () => (
|
||||
<StoryViewer
|
||||
{...getDefaultProps()}
|
||||
stories={[
|
||||
{
|
||||
attachment: fakeAttachment({
|
||||
caption: 'This place looks lovely',
|
||||
url: '/fixtures/nathan-anderson-316188-unsplash.jpg',
|
||||
}),
|
||||
messageId: '123',
|
||||
sender: getDefaultConversation(),
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
));
|
||||
|
||||
story.add('Long Caption', () => (
|
||||
<StoryViewer
|
||||
{...getDefaultProps()}
|
||||
stories={[
|
||||
{
|
||||
attachment: fakeAttachment({
|
||||
caption:
|
||||
'Snowycle, snowycle, snowycle\nI want to ride my snowycle, snowycle, snowycle\nI want to ride my snowycle\nI want to ride my snow\nI want to ride my snowycle\nI want to ride it where I like\nSnowycle, snowycle, snowycle\nI want to ride my snowycle, snowycle, snowycle\nI want to ride my snowycle\nI want to ride my snow\nI want to ride my snowycle\nI want to ride it where I like\nSnowycle, snowycle, snowycle\nI want to ride my snowycle, snowycle, snowycle\nI want to ride my snowycle\nI want to ride my snow\nI want to ride my snowycle\nI want to ride it where I like',
|
||||
url: '/fixtures/snow.jpg',
|
||||
}),
|
||||
messageId: '123',
|
||||
sender: getDefaultConversation(),
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -17,6 +17,7 @@ import { MessageTimestamp } from './conversation/MessageTimestamp';
|
|||
import { StoryImage } from './StoryImage';
|
||||
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
|
||||
import { getAvatarColor } from '../types/Colors';
|
||||
import { getStoryBackground } from '../util/getStoryBackground';
|
||||
import { getStoryDuration } from '../util/getStoryDuration';
|
||||
import { graphemeAwareSlice } from '../util/graphemeAwareSlice';
|
||||
import { isDownloaded, isDownloading } from '../types/Attachment';
|
||||
|
@ -209,12 +210,12 @@ export const StoryViewer = ({
|
|||
}, [currentStoryIndex, spring, storyDuration]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasReplyModal) {
|
||||
if (hasReplyModal || hasExpandedCaption) {
|
||||
spring.pause();
|
||||
} else {
|
||||
spring.resume();
|
||||
}
|
||||
}, [hasReplyModal, spring]);
|
||||
}, [hasExpandedCaption, hasReplyModal, spring]);
|
||||
|
||||
useEffect(() => {
|
||||
markStoryRead(messageId);
|
||||
|
@ -273,21 +274,11 @@ export const StoryViewer = ({
|
|||
return (
|
||||
<FocusTrap focusTrapOptions={{ allowOutsideClick: true }}>
|
||||
<div className="StoryViewer">
|
||||
<div className="StoryViewer__overlay" />
|
||||
<div
|
||||
className="StoryViewer__overlay"
|
||||
style={{ background: getStoryBackground(attachment) }}
|
||||
/>
|
||||
<div className="StoryViewer__content">
|
||||
<button
|
||||
aria-label={i18n('MyStories__more')}
|
||||
className="StoryViewer__more"
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label={i18n('close')}
|
||||
className="StoryViewer__close-button"
|
||||
onClick={onClose}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
/>
|
||||
<div className="StoryViewer__container">
|
||||
<StoryImage
|
||||
attachment={attachment}
|
||||
|
@ -298,7 +289,12 @@ export const StoryViewer = ({
|
|||
storyId={messageId}
|
||||
/>
|
||||
{hasExpandedCaption && (
|
||||
<div className="StoryViewer__caption__overlay" />
|
||||
<button
|
||||
aria-label={i18n('close-popup')}
|
||||
className="StoryViewer__caption__overlay"
|
||||
onClick={() => setHasExpandedCaption(false)}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
<div className="StoryViewer__meta">
|
||||
{caption && (
|
||||
|
@ -391,54 +387,67 @@ export const StoryViewer = ({
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="StoryViewer__actions">
|
||||
{isMe ? (
|
||||
<>
|
||||
{viewCount &&
|
||||
(viewCount === 1 ? (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__views--singular"
|
||||
components={[<strong>{viewCount}</strong>]}
|
||||
/>
|
||||
) : (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__views--plural"
|
||||
components={[<strong>{viewCount}</strong>]}
|
||||
/>
|
||||
))}
|
||||
{viewCount && replyCount && ' '}
|
||||
{replyCount &&
|
||||
(replyCount === 1 ? (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__replies--singular"
|
||||
components={[<strong>{replyCount}</strong>]}
|
||||
/>
|
||||
) : (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__replies--plural"
|
||||
components={[<strong>{replyCount}</strong>]}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
canReply && (
|
||||
<button
|
||||
className="StoryViewer__reply"
|
||||
onClick={() => setHasReplyModal(true)}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
{i18n('StoryViewer__reply')}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="StoryViewer__actions">
|
||||
{isMe ? (
|
||||
<>
|
||||
{viewCount &&
|
||||
(viewCount === 1 ? (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__views--singular"
|
||||
components={[<strong>{viewCount}</strong>]}
|
||||
/>
|
||||
) : (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__views--plural"
|
||||
components={[<strong>{viewCount}</strong>]}
|
||||
/>
|
||||
))}
|
||||
{viewCount && replyCount && ' '}
|
||||
{replyCount &&
|
||||
(replyCount === 1 ? (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__replies--singular"
|
||||
components={[<strong>{replyCount}</strong>]}
|
||||
/>
|
||||
) : (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="MyStories__replies--plural"
|
||||
components={[<strong>{replyCount}</strong>]}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
canReply && (
|
||||
<button
|
||||
className="StoryViewer__reply"
|
||||
onClick={() => setHasReplyModal(true)}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
{i18n('StoryViewer__reply')}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
aria-label={i18n('MyStories__more')}
|
||||
className="StoryViewer__more"
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
/>
|
||||
<button
|
||||
aria-label={i18n('close')}
|
||||
className="StoryViewer__close-button"
|
||||
onClick={onClose}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
{hasReplyModal && canReply && (
|
||||
<StoryViewsNRepliesModal
|
||||
|
|
|
@ -13,6 +13,10 @@ import { TextAttachmentStyleType } from '../types/Attachment';
|
|||
import { count } from '../util/grapheme';
|
||||
import { getDomain } from '../types/LinkPreview';
|
||||
import { getFontNameByTextScript } from '../util/getFontNameByTextScript';
|
||||
import {
|
||||
getHexFromNumber,
|
||||
getBackgroundColor,
|
||||
} from '../util/getStoryBackground';
|
||||
|
||||
const renderNewLines: RenderTextCallbackType = ({
|
||||
text: textWithNewLines,
|
||||
|
@ -53,20 +57,6 @@ function getTextSize(text: string): TextSize {
|
|||
return TextSize.Small;
|
||||
}
|
||||
|
||||
function getHexFromNumber(color: number): string {
|
||||
return `#${color.toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function getBackground({ color, gradient }: TextAttachmentType): string {
|
||||
if (gradient) {
|
||||
return `linear-gradient(${gradient.angle}deg, ${getHexFromNumber(
|
||||
gradient.startColor || COLOR_WHITE_INT
|
||||
)}, ${getHexFromNumber(gradient.endColor || COLOR_WHITE_INT)})`;
|
||||
}
|
||||
|
||||
return getHexFromNumber(color || COLOR_WHITE_INT);
|
||||
}
|
||||
|
||||
function getFont(
|
||||
text: string,
|
||||
textSize: TextSize,
|
||||
|
@ -123,7 +113,7 @@ export const TextAttachment = ({
|
|||
<div
|
||||
className="TextAttachment__story"
|
||||
style={{
|
||||
background: getBackground(textAttachment),
|
||||
background: getBackgroundColor(textAttachment),
|
||||
transform: `scale(${(contentRect.bounds?.height || 1) / 1280})`,
|
||||
}}
|
||||
>
|
||||
|
|
40
ts/util/getStoryBackground.ts
Normal file
40
ts/util/getStoryBackground.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { AttachmentType, TextAttachmentType } from '../types/Attachment';
|
||||
|
||||
const COLOR_BLACK_ALPHA_90 = 'rgba(0, 0, 0, 0.9)';
|
||||
const COLOR_WHITE_INT = 4294704123;
|
||||
|
||||
export function getHexFromNumber(color: number): string {
|
||||
return `#${color.toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
export function getBackgroundColor({
|
||||
color,
|
||||
gradient,
|
||||
}: TextAttachmentType): string {
|
||||
if (gradient) {
|
||||
return `linear-gradient(${gradient.angle}deg, ${getHexFromNumber(
|
||||
gradient.startColor || COLOR_WHITE_INT
|
||||
)}, ${getHexFromNumber(gradient.endColor || COLOR_WHITE_INT)})`;
|
||||
}
|
||||
|
||||
return getHexFromNumber(color || COLOR_WHITE_INT);
|
||||
}
|
||||
|
||||
export function getStoryBackground(attachment?: AttachmentType): string {
|
||||
if (!attachment) {
|
||||
return COLOR_BLACK_ALPHA_90;
|
||||
}
|
||||
|
||||
if (attachment.textAttachment) {
|
||||
return getBackgroundColor(attachment.textAttachment);
|
||||
}
|
||||
|
||||
if (attachment.url) {
|
||||
return `url("${attachment.url}")`;
|
||||
}
|
||||
|
||||
return COLOR_BLACK_ALPHA_90;
|
||||
}
|
Loading…
Reference in a new issue