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 {
|
.StoryImage {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
|
|
||||||
.StoryViewer {
|
.StoryViewer {
|
||||||
&__overlay {
|
&__overlay {
|
||||||
background: $color-gray-95;
|
background-size: contain;
|
||||||
filter: blur(160px);
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -15,6 +14,8 @@
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
backdrop-filter: blur(90px);
|
||||||
|
background: $color-black-alpha-20;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
@ -29,16 +30,19 @@
|
||||||
&__close-button {
|
&__close-button {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
@include modal-close-button;
|
@include modal-close-button;
|
||||||
|
right: 28px;
|
||||||
top: var(--title-bar-drag-area-height);
|
top: var(--title-bar-drag-area-height);
|
||||||
|
z-index: $z-index-above-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__more {
|
&__more {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 48px;
|
right: 80px;
|
||||||
top: var(--title-bar-drag-area-height);
|
top: var(--title-bar-drag-area-height);
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
z-index: $z-index-above-base;
|
||||||
|
|
||||||
@include color-svg('../images/icons/v2/more-horiz-24.svg', $color-white);
|
@include color-svg('../images/icons/v2/more-horiz-24.svg', $color-white);
|
||||||
|
|
||||||
|
@ -51,14 +55,12 @@
|
||||||
|
|
||||||
&__container {
|
&__container {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin-top: 36px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: $z-index-base;
|
z-index: $z-index-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__story {
|
&__story {
|
||||||
border-radius: 12px;
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
@ -67,7 +69,7 @@
|
||||||
&__meta {
|
&__meta {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
padding: 16px;
|
padding: 0 16px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 284px;
|
width: 284px;
|
||||||
|
@ -94,8 +96,10 @@
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
color: $color-gray-05;
|
color: $color-gray-05;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
&__overlay {
|
&__overlay {
|
||||||
|
@include button-reset;
|
||||||
background: $color-black-alpha-60;
|
background: $color-black-alpha-60;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -107,11 +111,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 32px;
|
min-height: 60px;
|
||||||
min-height: 52px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__reply {
|
&__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 { StoryImage } from './StoryImage';
|
||||||
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
|
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
|
||||||
import { getAvatarColor } from '../types/Colors';
|
import { getAvatarColor } from '../types/Colors';
|
||||||
|
import { getStoryBackground } from '../util/getStoryBackground';
|
||||||
import { getStoryDuration } from '../util/getStoryDuration';
|
import { getStoryDuration } from '../util/getStoryDuration';
|
||||||
import { graphemeAwareSlice } from '../util/graphemeAwareSlice';
|
import { graphemeAwareSlice } from '../util/graphemeAwareSlice';
|
||||||
import { isDownloaded, isDownloading } from '../types/Attachment';
|
import { isDownloaded, isDownloading } from '../types/Attachment';
|
||||||
|
@ -209,12 +210,12 @@ export const StoryViewer = ({
|
||||||
}, [currentStoryIndex, spring, storyDuration]);
|
}, [currentStoryIndex, spring, storyDuration]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasReplyModal) {
|
if (hasReplyModal || hasExpandedCaption) {
|
||||||
spring.pause();
|
spring.pause();
|
||||||
} else {
|
} else {
|
||||||
spring.resume();
|
spring.resume();
|
||||||
}
|
}
|
||||||
}, [hasReplyModal, spring]);
|
}, [hasExpandedCaption, hasReplyModal, spring]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
markStoryRead(messageId);
|
markStoryRead(messageId);
|
||||||
|
@ -273,21 +274,11 @@ export const StoryViewer = ({
|
||||||
return (
|
return (
|
||||||
<FocusTrap focusTrapOptions={{ allowOutsideClick: true }}>
|
<FocusTrap focusTrapOptions={{ allowOutsideClick: true }}>
|
||||||
<div className="StoryViewer">
|
<div className="StoryViewer">
|
||||||
<div className="StoryViewer__overlay" />
|
<div
|
||||||
|
className="StoryViewer__overlay"
|
||||||
|
style={{ background: getStoryBackground(attachment) }}
|
||||||
|
/>
|
||||||
<div className="StoryViewer__content">
|
<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">
|
<div className="StoryViewer__container">
|
||||||
<StoryImage
|
<StoryImage
|
||||||
attachment={attachment}
|
attachment={attachment}
|
||||||
|
@ -298,7 +289,12 @@ export const StoryViewer = ({
|
||||||
storyId={messageId}
|
storyId={messageId}
|
||||||
/>
|
/>
|
||||||
{hasExpandedCaption && (
|
{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">
|
<div className="StoryViewer__meta">
|
||||||
{caption && (
|
{caption && (
|
||||||
|
@ -391,54 +387,67 @@ export const StoryViewer = ({
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="StoryViewer__actions">
|
<button
|
||||||
{isMe ? (
|
aria-label={i18n('MyStories__more')}
|
||||||
<>
|
className="StoryViewer__more"
|
||||||
{viewCount &&
|
tabIndex={0}
|
||||||
(viewCount === 1 ? (
|
type="button"
|
||||||
<Intl
|
/>
|
||||||
i18n={i18n}
|
<button
|
||||||
id="MyStories__views--singular"
|
aria-label={i18n('close')}
|
||||||
components={[<strong>{viewCount}</strong>]}
|
className="StoryViewer__close-button"
|
||||||
/>
|
onClick={onClose}
|
||||||
) : (
|
tabIndex={0}
|
||||||
<Intl
|
type="button"
|
||||||
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>
|
||||||
{hasReplyModal && canReply && (
|
{hasReplyModal && canReply && (
|
||||||
<StoryViewsNRepliesModal
|
<StoryViewsNRepliesModal
|
||||||
|
|
|
@ -13,6 +13,10 @@ import { TextAttachmentStyleType } from '../types/Attachment';
|
||||||
import { count } from '../util/grapheme';
|
import { count } from '../util/grapheme';
|
||||||
import { getDomain } from '../types/LinkPreview';
|
import { getDomain } from '../types/LinkPreview';
|
||||||
import { getFontNameByTextScript } from '../util/getFontNameByTextScript';
|
import { getFontNameByTextScript } from '../util/getFontNameByTextScript';
|
||||||
|
import {
|
||||||
|
getHexFromNumber,
|
||||||
|
getBackgroundColor,
|
||||||
|
} from '../util/getStoryBackground';
|
||||||
|
|
||||||
const renderNewLines: RenderTextCallbackType = ({
|
const renderNewLines: RenderTextCallbackType = ({
|
||||||
text: textWithNewLines,
|
text: textWithNewLines,
|
||||||
|
@ -53,20 +57,6 @@ function getTextSize(text: string): TextSize {
|
||||||
return TextSize.Small;
|
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(
|
function getFont(
|
||||||
text: string,
|
text: string,
|
||||||
textSize: TextSize,
|
textSize: TextSize,
|
||||||
|
@ -123,7 +113,7 @@ export const TextAttachment = ({
|
||||||
<div
|
<div
|
||||||
className="TextAttachment__story"
|
className="TextAttachment__story"
|
||||||
style={{
|
style={{
|
||||||
background: getBackground(textAttachment),
|
background: getBackgroundColor(textAttachment),
|
||||||
transform: `scale(${(contentRect.bounds?.height || 1) / 1280})`,
|
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