From 87c7571d5cd896645a3381ea75877b4af54d4af8 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sun, 5 Jan 2025 17:28:35 -0800 Subject: [PATCH] embed screen style adjustments (#4063) * restyle and relayout embed screen to account for smaller screen displays. - address https://github.com/owncast/owncast/issues/3683 to address overflow issues - address https://github.com/owncast/owncast/issues/4051 to move the name of the stream * Javascript formatting autofixes * clean up; restore package lock * accommodate cases when there's no follow option; put follow form on one line, but wrap if need * clean up * separate out follow form into separate standalone component to be used in multiple places * improve follow error styling; rm defaultProps for Modal to get rid of warning * improve styling of follow form and components for legibility * prettyify scss * prettyify scss again * one more time * prettify ant file * simplify layout, center everything * just use gap * tweak and lint * lint, again --------- Co-authored-by: Owncast --- .../modals/FollowModal/FollowForm.tsx | 103 +++++++++++++ .../FollowModal/FollowModal.module.scss | 35 ++++- .../modals/FollowModal/FollowModal.tsx | 128 +++------------- web/components/ui/Modal/Modal.tsx | 8 - .../ui/OfflineEmbed/OfflineEmbed.module.scss | 114 +++++++++++---- .../ui/OfflineEmbed/OfflineEmbed.tsx | 137 ++++-------------- web/styles/ant-overrides.scss | 6 +- 7 files changed, 270 insertions(+), 261 deletions(-) create mode 100644 web/components/modals/FollowModal/FollowForm.tsx diff --git a/web/components/modals/FollowModal/FollowForm.tsx b/web/components/modals/FollowModal/FollowForm.tsx new file mode 100644 index 000000000..5e0e7cc16 --- /dev/null +++ b/web/components/modals/FollowModal/FollowForm.tsx @@ -0,0 +1,103 @@ +/* eslint-disable react/no-unescaped-entities */ +import { Input, Button, Alert, Spin, Space } from 'antd'; +import { FC, useState } from 'react'; +import styles from './FollowModal.module.scss'; +import { isValidFediverseAccount } from '../../../utils/validators'; + +const ENDPOINT = '/api/remotefollow'; + +export type FollowFormProps = { + handleClose?: () => void; +}; + +export const FollowForm: FC = ({ handleClose }: FollowFormProps) => { + const [remoteAccount, setRemoteAccount] = useState(null); + const [valid, setValid] = useState(false); + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const handleAccountChange = a => { + setRemoteAccount(a); + if (isValidFediverseAccount(a)) { + setValid(true); + } else { + setValid(false); + } + }; + + const joinButtonPressed = () => { + window.open('https://owncast.online/join-fediverse', '_blank'); + }; + + const remoteFollowButtonPressed = async () => { + if (!valid) { + return; + } + + setLoading(true); + + try { + const sanitizedAccount = remoteAccount.replace(/^@+/, ''); + const request = { account: sanitizedAccount }; + const rawResponse = await fetch(ENDPOINT, { + method: 'POST', + body: JSON.stringify(request), + }); + const result = await rawResponse.json(); + + if (result.redirectUrl) { + window.open(result.redirectUrl, '_blank'); + handleClose(); + } + if (!result.success) { + setErrorMessage(result.message); + setLoading(false); + return; + } + if (!result.redirectUrl) { + setErrorMessage('Unable to follow.'); + setLoading(false); + return; + } + } catch (e) { + setErrorMessage(e.message); + } + setLoading(false); + }; + + return ( + + {errorMessage && ( + + )} + +
+
Enter your username @server to follow
+ handleAccountChange(e.target.value)} + placeholder="Your fediverse account @account@server" + defaultValue={remoteAccount} + /> +
+ You'll be redirected to your Fediverse server and asked to confirm the action. +
+
+ + + + +
+ ); +}; diff --git a/web/components/modals/FollowModal/FollowModal.module.scss b/web/components/modals/FollowModal/FollowModal.module.scss index cec2aa3bd..176839b24 100644 --- a/web/components/modals/FollowModal/FollowModal.module.scss +++ b/web/components/modals/FollowModal/FollowModal.module.scss @@ -9,16 +9,21 @@ margin-top: 10px; } -.instructions { +.inputContainer { font-family: var(--theme-text-display-font-family); - font-size: 0.7rem; - font-weight: 600; - margin-top: 5px; - margin-bottom: 5px; -} + margin-bottom: 10px; -.footer { - font-size: 0.5rem; + .instructions { + font-size: 14px; + font-weight: 600; + margin: 5px 2px; + } + + .footer { + font-size: 10px; + margin: 2px; + color: var(--theme-color-components-primary-button-text-disabled); + } } .account { @@ -50,3 +55,17 @@ } } } + +.errorAlert { + margin-bottom: 1.25rem; + font-family: var(--theme-text-display-font-family); + + :global(.ant-alert-message) { + font-size: 14px; + } + + :global(.ant-alert-description) { + font-size: 12px; + font-family: monospace; + } +} diff --git a/web/components/modals/FollowModal/FollowModal.tsx b/web/components/modals/FollowModal/FollowModal.tsx index e9ac40d3d..ab747b402 100644 --- a/web/components/modals/FollowModal/FollowModal.tsx +++ b/web/components/modals/FollowModal/FollowModal.tsx @@ -1,10 +1,8 @@ /* eslint-disable react/no-unescaped-entities */ -import { Input, Button, Alert, Spin, Space } from 'antd'; -import { FC, useState } from 'react'; +import { Space } from 'antd'; +import { FC } from 'react'; import styles from './FollowModal.module.scss'; -import { isValidFediverseAccount } from '../../../utils/validators'; - -const ENDPOINT = '/api/remotefollow'; +import { FollowForm } from './FollowForm'; export type FollowModalProps = { handleClose: () => void; @@ -12,106 +10,24 @@ export type FollowModalProps = { name: string; }; -export const FollowModal: FC = ({ handleClose, account, name }) => { - const [remoteAccount, setRemoteAccount] = useState(null); - const [valid, setValid] = useState(false); - const [loading, setLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(null); - - const handleAccountChange = a => { - setRemoteAccount(a); - if (isValidFediverseAccount(a)) { - setValid(true); - } else { - setValid(false); - } - }; - - const joinButtonPressed = () => { - window.open('https://owncast.online/join-fediverse', '_blank'); - }; - - const remoteFollowButtonPressed = async () => { - if (!valid) { - return; - } - - setLoading(true); - - try { - const sanitizedAccount = remoteAccount.replace(/^@+/, ''); - const request = { account: sanitizedAccount }; - const rawResponse = await fetch(ENDPOINT, { - method: 'POST', - body: JSON.stringify(request), - }); - const result = await rawResponse.json(); - - if (result.redirectUrl) { - window.open(result.redirectUrl, '_blank'); - handleClose(); - } - if (!result.success) { - setErrorMessage(result.message); - setLoading(false); - return; - } - if (!result.redirectUrl) { - setErrorMessage('Unable to follow.'); - setLoading(false); - return; - } - } catch (e) { - setErrorMessage(e.message); - } - setLoading(false); - }; - - return ( - -
- By following this stream you'll get notified on the Fediverse when it goes live. Now is a - great time to - -  learn about the Fediverse  - - if it's new to you. +export const FollowModal: FC = ({ handleClose, account, name }) => ( + +
+ By following this stream you'll get notified on the Fediverse when it goes live. Now is a + great time to + +  learn about the Fediverse  + + if it's new to you. +
+
+ logo +
+
{name}
+
{account}
+
- - {errorMessage && ( - - )} -
- logo -
-
{name}
-
{account}
-
-
- -
-
Enter your username @server to follow
- handleAccountChange(e.target.value)} - placeholder="Your fediverse account @account@server" - defaultValue={remoteAccount} - /> -
- You'll be redirected to your Fediverse server and asked to confirm the action. -
-
- - - - -
-
- ); -}; + + +); diff --git a/web/components/ui/Modal/Modal.tsx b/web/components/ui/Modal/Modal.tsx index 049e785e0..7f87cc77d 100644 --- a/web/components/ui/Modal/Modal.tsx +++ b/web/components/ui/Modal/Modal.tsx @@ -95,11 +95,3 @@ export const Modal: FC = ({ ); }; - -Modal.defaultProps = { - url: undefined, - children: undefined, - handleOk: undefined, - handleCancel: undefined, - afterClose: undefined, -}; diff --git a/web/components/ui/OfflineEmbed/OfflineEmbed.module.scss b/web/components/ui/OfflineEmbed/OfflineEmbed.module.scss index 1097ac135..3c9ec40ad 100644 --- a/web/components/ui/OfflineEmbed/OfflineEmbed.module.scss +++ b/web/components/ui/OfflineEmbed/OfflineEmbed.module.scss @@ -1,6 +1,12 @@ @import '../../../styles/mixins'; +$short-container-max-height: 480px; +$short-container-min-height: 320px; +$follow-modal-width: 300px; + .offlineContainer { + --text-color: rgb(255 255 255 / 100%); + position: absolute; width: 100%; height: 100%; @@ -13,56 +19,108 @@ align-items: center; justify-content: center; gap: 16px; - padding: 24px; + font-size: 16px; + + @include screen(mobile) { + font-size: 12px; + } + + @media (height <= $short-container-max-height) { + font-size: 12px; + } /* Content */ .content { + color: var(--text-color); display: flex; flex-flow: column nowrap; align-items: center; - justify-content: center; - gap: 8px; + gap: 2rem; padding: 24px; text-align: center; + width: 100vw; + height: 100vh; + max-width: 1024px; + max-height: 576px; + justify-content: center; + + .headerContainer { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + margin: 0; + font-size: 1em; + } + + .messageContainer { + width: 100%; + + --description-height: auto; + + @media (height <= $short-container-min-height) { + --description-height: 30vh; + + margin: 0; + } + + min-height: var(--description-height); + + --gradient-mask: linear-gradient(to top, rgb(0 0 0 / 0%), rgb(0 0 0 / 100%) 17%); + + overflow: hidden; + overflow-y: auto; + max-height: var(--description-height); + mask-image: var(--gradient-mask); + mask-repeat: repeat-x; + mask-size: auto var(--description-height); + mask-position: top; + padding-bottom: 0.5rem; + } /* Message */ .message { - color: rgb(255 255 255 / 100%); + color: var(--text-color); font-family: var(--theme-text-body-font-family); font-style: normal; - font-size: 16px; - font-weight: 400; + font-size: 1em; line-height: 1.375; letter-spacing: 0; text-decoration: none; text-transform: none; + display: block; + margin: auto; + + @include screen(desktop) { + width: 80%; + } } /* Heading */ - .heading { - color: rgb(255 255 255 / 100%); + .offlineTitle { + color: var(--text-color); font-family: var(--theme-text-display-font-family); font-style: normal; - font-size: 24px; - font-weight: 500; + font-size: 1.375em; + font-weight: 600; line-height: 1.125; letter-spacing: -0.125px; text-decoration: none; text-transform: none; + margin: 0; } /* Page Logo */ .pageLogo { position: relative; - width: 10vw; - height: 10vw; - min-height: 64px; - min-width: 64px; + min-height: 3em; + min-width: 3em; max-height: 100px; max-width: 100px; border-radius: 96px; background-color: rgb(255 255 255 / 100%); - border: 5px solid rgb(18 22 29 / 100%); + border: 2px solid rgb(18 22 29 / 100%); display: flex; flex-flow: row nowrap; align-items: flex-start; @@ -73,26 +131,26 @@ background-position: center; } - /* Page Name */ - .pageName { - color: rgb(255 255 255 / 100%); + /* Stream Name */ + .streamName { + color: var(--text-color); font-family: var(--theme-text-display-font-family); font-style: normal; - font-size: 20px; + font-size: 1.25em; font-weight: 500; line-height: 1.1875; letter-spacing: -0.0625px; text-decoration: none; text-transform: none; + text-align: left; + } + + .followButton { + display: none; + + @media (width > $follow-modal-width) { + display: block; + } } } - - .submitButton { - margin-top: 10px; - } - - .footer { - color: white; - padding: 5px; - } } diff --git a/web/components/ui/OfflineEmbed/OfflineEmbed.tsx b/web/components/ui/OfflineEmbed/OfflineEmbed.tsx index e8ecd8f0e..2730a241f 100644 --- a/web/components/ui/OfflineEmbed/OfflineEmbed.tsx +++ b/web/components/ui/OfflineEmbed/OfflineEmbed.tsx @@ -3,12 +3,12 @@ import { FC, useEffect, useState } from 'react'; import classNames from 'classnames'; import Head from 'next/head'; -import { Button, Input, Space, Spin, Alert, Typography } from 'antd'; +import { Button, Typography } from 'antd'; import styles from './OfflineEmbed.module.scss'; -import { isValidFediverseAccount } from '../../../utils/validators'; +import { Modal } from '../Modal/Modal'; +import { FollowForm } from '../../modals/FollowModal/FollowForm'; const { Title } = Typography; -const ENDPOINT = '/api/remotefollow'; export type OfflineEmbedProps = { streamName: string; @@ -20,8 +20,6 @@ export type OfflineEmbedProps = { enum EmbedMode { CannotFollow = 1, CanFollow, - FollowPrompt, - InProgress, } export const OfflineEmbed: FC = ({ @@ -31,10 +29,7 @@ export const OfflineEmbed: FC = ({ supportsFollows, }) => { const [currentMode, setCurrentMode] = useState(EmbedMode.CanFollow); - const [remoteAccount, setRemoteAccount] = useState(null); - const [valid, setValid] = useState(false); - const [loading, setLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(null); + const [showFollowModal, setShowFollowModal] = useState(false); useEffect(() => { if (!supportsFollows) { @@ -45,53 +40,7 @@ export const OfflineEmbed: FC = ({ }, [supportsFollows]); const followButtonPressed = async () => { - setCurrentMode(EmbedMode.FollowPrompt); - }; - - const remoteFollowButtonPressed = async () => { - setLoading(true); - setCurrentMode(EmbedMode.CannotFollow); - - try { - const sanitizedAccount = remoteAccount.replace(/^@+/, ''); - const request = { account: sanitizedAccount }; - const rawResponse = await fetch(ENDPOINT, { - method: 'POST', - body: JSON.stringify(request), - }); - const result = await rawResponse.json(); - - if (result.redirectUrl) { - window.open(result.redirectUrl, '_blank'); - } - if (!result.success) { - setErrorMessage(result.message); - setLoading(false); - return; - } - if (!result.redirectUrl) { - setErrorMessage('Unable to follow.'); - setLoading(false); - return; - } - } catch (e) { - setErrorMessage(e.message); - } - setLoading(false); - }; - - const handleErrorClose = () => { - setErrorMessage(''); - setCurrentMode(EmbedMode.FollowPrompt); - }; - - const handleAccountChange = a => { - setRemoteAccount(a); - if (isValidFediverseAccount(a)) { - setValid(true); - } else { - setValid(false); - } + setShowFollowModal(true); }; return ( @@ -100,64 +49,34 @@ export const OfflineEmbed: FC = ({ {streamName}
- -
-
This stream is not currently live.
-
- +
+ <div className={styles.pageLogo} style={{ backgroundImage: `url(${image})` }} /> - <div className={styles.pageName}>{streamName}</div> + <div className={styles.streamName}>{streamName}</div> + - {errorMessage && ( - - )} +
+ + This stream is not currently live. + +
+
- {currentMode === EmbedMode.CanFollow && ( - - )} - - {currentMode === EmbedMode.InProgress && ( - - Follow the instructions on your Fediverse server to complete the follow. - - )} - - {currentMode === EmbedMode.FollowPrompt && ( -
- handleAccountChange(e.target.value)} - placeholder="Your fediverse account @account@server" - defaultValue={remoteAccount} - /> -
- You'll be redirected to your Fediverse server and asked to confirm the - action. -
- - - -
- )} -
- + setShowFollowModal(false)} + > + + + + )} +
); diff --git a/web/styles/ant-overrides.scss b/web/styles/ant-overrides.scss index 2f1305d6c..3228c9350 100644 --- a/web/styles/ant-overrides.scss +++ b/web/styles/ant-overrides.scss @@ -11,6 +11,10 @@ HEADER BUTTONS // ------------------------- */ +.ant-btn { + font-size: 0.85rem; +} + .ant-btn[disabled] { background-color: var(--theme-color-components-secondary-button-background-disabled); color: var(--theme-color-components-secondary-button-text-disabled); @@ -40,8 +44,6 @@ BUTTONS } .ant-btn-primary { - height: 2rem; - font-size: 0.85rem; border-width: 2px; border-radius: var(--theme-rounded-corners); border-color: var(--theme-color-components-primary-button-border);