* fix: #2668 Page Vertical Spacing Issues * Update test to reflect mobile work * chore: refactor action buttons --------- Co-authored-by: thisProjects <wibbet@wobbet.com> Co-authored-by: Gabe Kangas <gabek@real-ity.com>
This commit is contained in:
parent
b0a43526d8
commit
32c3f6a9b6
@ -15,8 +15,8 @@ describe(`Basic tests`, () => {
|
|||||||
|
|
||||||
// Verify the tags show up
|
// Verify the tags show up
|
||||||
it('Has correct tags visible', () => {
|
it('Has correct tags visible', () => {
|
||||||
cy.contains('#owncast').should('be.visible');
|
cy.contains('#owncast').should('exist');
|
||||||
cy.contains('#streaming').should('be.visible');
|
cy.contains('#streaming').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
// it('Can open notify modal', () => {
|
// it('Can open notify modal', () => {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
// The button that is displayed to scroll to the bottom of the chat.
|
// The button that is displayed to scroll to the bottom of the chat.
|
||||||
.toBottomWrap {
|
.toBottomWrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
@import '../../../styles/mixins.scss';
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
|
// this margin is for fixed header
|
||||||
|
margin-top: 55px;
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
// this one is for fixed footer
|
||||||
|
margin-bottom: 30px
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { Layout } from 'antd';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { Footer } from '../../ui/Footer/Footer';
|
||||||
import {
|
import {
|
||||||
ClientConfigStore,
|
ClientConfigStore,
|
||||||
isChatAvailableSelector,
|
isChatAvailableSelector,
|
||||||
@ -30,12 +31,6 @@ import { AppStateOptions } from '../../stores/application-state';
|
|||||||
import { Noscript } from '../../ui/Noscript/Noscript';
|
import { Noscript } from '../../ui/Noscript/Noscript';
|
||||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||||
|
|
||||||
const lockBodyStyle = `
|
|
||||||
body {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Lazy loaded components
|
// Lazy loaded components
|
||||||
|
|
||||||
const FatalErrorStateModal = dynamic(
|
const FatalErrorStateModal = dynamic(
|
||||||
@ -51,13 +46,13 @@ const FatalErrorStateModal = dynamic(
|
|||||||
export const Main: FC = () => {
|
export const Main: FC = () => {
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||||
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
const { name, customStyles } = clientConfig;
|
const { name } = clientConfig;
|
||||||
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
||||||
const fatalError = useRecoilValue<DisplayableError>(fatalErrorStateAtom);
|
const fatalError = useRecoilValue<DisplayableError>(fatalErrorStateAtom);
|
||||||
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
||||||
|
|
||||||
const layoutRef = useRef<HTMLDivElement>(null);
|
const layoutRef = useRef<HTMLDivElement>(null);
|
||||||
const { chatDisabled } = clientConfig;
|
const { chatDisabled, version } = clientConfig;
|
||||||
const { videoAvailable } = appState;
|
const { videoAvailable } = appState;
|
||||||
const { online, streamTitle } = clientStatus;
|
const { online, streamTitle } = clientStatus;
|
||||||
|
|
||||||
@ -96,11 +91,6 @@ export const Main: FC = () => {
|
|||||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||||
<meta name="msapplication-TileImage" content="/img/favicon/ms-icon-144x144.png" />
|
<meta name="msapplication-TileImage" content="/img/favicon/ms-icon-144x144.png" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
|
|
||||||
<style>
|
|
||||||
{customStyles}
|
|
||||||
{lockBodyStyle}
|
|
||||||
</style>
|
|
||||||
<base target="_blank" />
|
<base target="_blank" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
@ -168,6 +158,7 @@ export const Main: FC = () => {
|
|||||||
{fatalError && (
|
{fatalError && (
|
||||||
<FatalErrorStateModal title={fatalError.title} message={fatalError.message} />
|
<FatalErrorStateModal title={fatalError.title} message={fatalError.message} />
|
||||||
)}
|
)}
|
||||||
|
<Footer version={version} />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Noscript />
|
<Noscript />
|
||||||
|
88
web/components/ui/Content/ActionButtons.tsx
Normal file
88
web/components/ui/Content/ActionButtons.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Dispatch, FC, SetStateAction } from 'react';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { Skeleton } from 'antd';
|
||||||
|
import { ExternalAction } from '../../../interfaces/external-action';
|
||||||
|
import { ActionButtonMenu } from '../../action-buttons/ActionButtonMenu/ActionButtonMenu';
|
||||||
|
import { ActionButtonRow } from '../../action-buttons/ActionButtonRow/ActionButtonRow';
|
||||||
|
import { FollowButton } from '../../action-buttons/FollowButton';
|
||||||
|
import { NotifyButton } from '../../action-buttons/NotifyButton';
|
||||||
|
import styles from './Content.module.scss';
|
||||||
|
import { ActionButton } from '../../action-buttons/ActionButton/ActionButton';
|
||||||
|
|
||||||
|
interface ActionButtonProps {
|
||||||
|
isMobile: boolean;
|
||||||
|
supportFediverseFeatures: boolean;
|
||||||
|
externalActions: ExternalAction[];
|
||||||
|
supportsBrowserNotifications: boolean;
|
||||||
|
showNotifyReminder: any;
|
||||||
|
setShowFollowModal: Dispatch<SetStateAction<boolean>>;
|
||||||
|
setShowNotifyModal: Dispatch<SetStateAction<boolean>>;
|
||||||
|
disableNotifyReminderPopup: () => void;
|
||||||
|
setExternalActionToDisplay: any;
|
||||||
|
externalActionSelected: (action: ExternalAction) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NotifyReminderPopup = dynamic(
|
||||||
|
() => import('../NotifyReminderPopup/NotifyReminderPopup').then(mod => mod.NotifyReminderPopup),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => <Skeleton loading active paragraph={{ rows: 8 }} />,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const ActionButtons: FC<ActionButtonProps> = ({
|
||||||
|
isMobile,
|
||||||
|
supportFediverseFeatures,
|
||||||
|
supportsBrowserNotifications,
|
||||||
|
showNotifyReminder,
|
||||||
|
setShowFollowModal,
|
||||||
|
setShowNotifyModal,
|
||||||
|
disableNotifyReminderPopup,
|
||||||
|
externalActions,
|
||||||
|
setExternalActionToDisplay,
|
||||||
|
externalActionSelected,
|
||||||
|
}) => {
|
||||||
|
const externalActionButtons = externalActions.map(action => (
|
||||||
|
<ActionButton
|
||||||
|
key={action.url || action.html}
|
||||||
|
action={action}
|
||||||
|
externalActionSelected={externalActionSelected}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!isMobile) {
|
||||||
|
return (
|
||||||
|
<ActionButtonRow>
|
||||||
|
{externalActionButtons}
|
||||||
|
{supportFediverseFeatures && (
|
||||||
|
<FollowButton size="small" onClick={() => setShowFollowModal(true)} />
|
||||||
|
)}
|
||||||
|
{supportsBrowserNotifications && (
|
||||||
|
<NotifyReminderPopup
|
||||||
|
open={showNotifyReminder}
|
||||||
|
notificationClicked={() => setShowNotifyModal(true)}
|
||||||
|
notificationClosed={() => disableNotifyReminderPopup()}
|
||||||
|
>
|
||||||
|
<NotifyButton onClick={() => setShowNotifyModal(true)} />
|
||||||
|
</NotifyReminderPopup>
|
||||||
|
)}
|
||||||
|
</ActionButtonRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.mobileActionButtonMenu}>
|
||||||
|
<ActionButtonMenu
|
||||||
|
className={styles.actionButtonMenu}
|
||||||
|
showFollowItem={supportFediverseFeatures}
|
||||||
|
showNotifyItem={supportsBrowserNotifications}
|
||||||
|
actions={externalActions}
|
||||||
|
externalActionSelected={setExternalActionToDisplay}
|
||||||
|
notifyItemSelected={() => setShowNotifyModal(true)}
|
||||||
|
followItemSelected={() => setShowFollowModal(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionButtons;
|
@ -1,59 +1,5 @@
|
|||||||
@import '../../../styles/mixins.scss';
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.root {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
grid-template-rows: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--theme-color-background-main);
|
|
||||||
height: 100%;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
@include screen(desktop) {
|
|
||||||
height: var(--content-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainSection {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: min-content // Skeleton when app is loading
|
|
||||||
minmax(30px, min-content) // player
|
|
||||||
min-content // status bar when live
|
|
||||||
min-content // mid section
|
|
||||||
minmax(250px, 1fr) // mobile content
|
|
||||||
;
|
|
||||||
grid-template-columns: 100%;
|
|
||||||
|
|
||||||
&.offline {
|
|
||||||
grid-template-rows: min-content // Skeleton when app is loading
|
|
||||||
min-content // offline banner
|
|
||||||
min-content // status bar when live
|
|
||||||
min-content // mid section
|
|
||||||
minmax(250px, 1fr) // mobile content
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include screen(tablet) {
|
|
||||||
grid-template-columns: 100vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include screen(desktop) {
|
|
||||||
overflow-y: scroll;
|
|
||||||
grid-template-rows: unset;
|
|
||||||
|
|
||||||
&.offline {
|
|
||||||
grid-template-rows: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainSection::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainSection::-webkit-scrollbar-thumb {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lowerSection {
|
.lowerSection {
|
||||||
padding: var(--content-padding);
|
padding: var(--content-padding);
|
||||||
}
|
}
|
||||||
@ -64,72 +10,60 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@include screen(tablet) {
|
||||||
|
//sets the position of tabbed content for online mode
|
||||||
|
top: 430px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@include screen(mobile) {
|
||||||
|
//sets the position of tabbed content for online mode
|
||||||
|
top: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileNoTabs {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.topSectionElement {
|
.topSectionElement {
|
||||||
background-color: var(--theme-color-components-video-background);
|
background-color: var(--theme-color-components-video-background);
|
||||||
|
@include screen(tablet) {
|
||||||
|
// "sticks" the stream to the top of the page
|
||||||
|
position: sticky;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.offlineBanner {
|
.offlineBanner {
|
||||||
color: var(--theme-color-background-main);
|
color: var(--theme-color-background-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobileActionButtonMenu {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
//not sure if this is needed
|
||||||
.statusBar {
|
.statusBar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftCol {
|
// not sure if this is needed
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadingSpinner {
|
.loadingSpinner {
|
||||||
display: grid;
|
display: grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.replacementBar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
@include screen(tablet) {
|
|
||||||
height: var(--replacement-bar-height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.defaultTabBar {
|
.defaultTabBar {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.defaultTabBar,
|
|
||||||
.actionButtonMenu {
|
|
||||||
@include screen(tablet) {
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
padding-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@include screen(tablet) {
|
|
||||||
position: relative;
|
|
||||||
> :global(.ant-tabs-content-holder) {
|
|
||||||
position: absolute;
|
|
||||||
height: calc(100% - var(--replacement-bar-height));
|
|
||||||
top: var(--replacement-bar-height);
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> :global(.ant-tabs-content-holder .ant-tabs-content) {
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottomPageContentContainer {
|
.bottomPageContentContainer {
|
||||||
background-color: var(--theme-color-components-content-background);
|
background-color: var(--theme-color-components-content-background);
|
||||||
padding: calc(2 * var(--content-padding));
|
padding: calc(2 * var(--content-padding));
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { Skeleton } from 'antd';
|
import { Skeleton, Col, Row } from 'antd';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import ActionButtons from './ActionButtons';
|
||||||
import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage';
|
import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../utils/localStorage';
|
||||||
import isPushNotificationSupported from '../../../utils/browserPushNotifications';
|
import isPushNotificationSupported from '../../../utils/browserPushNotifications';
|
||||||
|
|
||||||
@ -21,14 +22,8 @@ import { ClientConfig } from '../../../interfaces/client-config.model';
|
|||||||
|
|
||||||
import styles from './Content.module.scss';
|
import styles from './Content.module.scss';
|
||||||
import { Sidebar } from '../Sidebar/Sidebar';
|
import { Sidebar } from '../Sidebar/Sidebar';
|
||||||
import { Footer } from '../Footer/Footer';
|
|
||||||
|
|
||||||
import { ActionButtonRow } from '../../action-buttons/ActionButtonRow/ActionButtonRow';
|
|
||||||
import { ActionButton } from '../../action-buttons/ActionButton/ActionButton';
|
|
||||||
import { OfflineBanner } from '../OfflineBanner/OfflineBanner';
|
import { OfflineBanner } from '../OfflineBanner/OfflineBanner';
|
||||||
import { AppStateOptions } from '../../stores/application-state';
|
import { AppStateOptions } from '../../stores/application-state';
|
||||||
import { FollowButton } from '../../action-buttons/FollowButton';
|
|
||||||
import { NotifyButton } from '../../action-buttons/NotifyButton';
|
|
||||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||||
import { Statusbar } from '../Statusbar/Statusbar';
|
import { Statusbar } from '../Statusbar/Statusbar';
|
||||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||||
@ -58,14 +53,6 @@ const BrowserNotifyModal = dynamic(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const NotifyReminderPopup = dynamic(
|
|
||||||
() => import('../NotifyReminderPopup/NotifyReminderPopup').then(mod => mod.NotifyReminderPopup),
|
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
loading: () => <Skeleton loading active paragraph={{ rows: 8 }} />,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const OwncastPlayer = dynamic(
|
const OwncastPlayer = dynamic(
|
||||||
() => import('../../video/OwncastPlayer/OwncastPlayer').then(mod => mod.OwncastPlayer),
|
() => import('../../video/OwncastPlayer/OwncastPlayer').then(mod => mod.OwncastPlayer),
|
||||||
{
|
{
|
||||||
@ -114,7 +101,6 @@ export const Content: FC = () => {
|
|||||||
useRecoilValue<ServerStatus>(serverStatusState);
|
useRecoilValue<ServerStatus>(serverStatusState);
|
||||||
const {
|
const {
|
||||||
extraPageContent,
|
extraPageContent,
|
||||||
version,
|
|
||||||
name,
|
name,
|
||||||
summary,
|
summary,
|
||||||
socialHandles,
|
socialHandles,
|
||||||
@ -147,14 +133,6 @@ export const Content: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const externalActionButtons = externalActions.map(action => (
|
|
||||||
<ActionButton
|
|
||||||
key={action.url || action.html}
|
|
||||||
action={action}
|
|
||||||
externalActionSelected={externalActionSelected}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
const incrementVisitCounter = () => {
|
const incrementVisitCounter = () => {
|
||||||
let visits = parseInt(getLocalStorage(LOCAL_STORAGE_KEYS.userVisitCount), 10);
|
let visits = parseInt(getLocalStorage(LOCAL_STORAGE_KEYS.userVisitCount), 10);
|
||||||
if (Number.isNaN(visits)) {
|
if (Number.isNaN(visits)) {
|
||||||
@ -201,79 +179,82 @@ export const Content: FC = () => {
|
|||||||
|
|
||||||
const showChat = online && !chatDisabled && isChatVisible;
|
const showChat = online && !chatDisabled && isChatVisible;
|
||||||
|
|
||||||
|
// accounts for sidebar width when online in desktop
|
||||||
|
const dynamicPadding = online && !isMobile ? '320px' : '0px';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.root}>
|
<>
|
||||||
<div className={classnames(styles.mainSection, { [styles.offline]: !online })}>
|
{appState.appLoading && (
|
||||||
{appState.appLoading ? (
|
<Skeleton loading active paragraph={{ rows: 7 }} className={styles.topSectionElement} />
|
||||||
<Skeleton loading active paragraph={{ rows: 7 }} className={styles.topSectionElement} />
|
)}
|
||||||
) : (
|
{showChat && !isMobile && <Sidebar />}
|
||||||
<div className="skeleton-placeholder" />
|
<Row>
|
||||||
)}
|
<Col span={24} style={{ paddingRight: dynamicPadding }}>
|
||||||
{online && (
|
{online && (
|
||||||
<OwncastPlayer
|
<OwncastPlayer
|
||||||
source="/hls/stream.m3u8"
|
source="/hls/stream.m3u8"
|
||||||
online={online}
|
online={online}
|
||||||
title={streamTitle || name}
|
title={streamTitle || name}
|
||||||
className={styles.topSectionElement}
|
className={styles.topSectionElement}
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!online && !appState.appLoading && (
|
|
||||||
<div id="offline-message">
|
|
||||||
<OfflineBanner
|
|
||||||
showsHeader={false}
|
|
||||||
streamName={name}
|
|
||||||
customText={offlineMessage}
|
|
||||||
notificationsEnabled={supportsBrowserNotifications}
|
|
||||||
fediverseAccount={fediverseAccount}
|
|
||||||
lastLive={lastDisconnectTime}
|
|
||||||
onNotifyClick={() => setShowNotifyModal(true)}
|
|
||||||
onFollowClick={() => setShowFollowModal(true)}
|
|
||||||
className={classnames([styles.topSectionElement, styles.offlineBanner])}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)}
|
{!online && !appState.appLoading && (
|
||||||
{isStreamLive ? (
|
<div id="offline-message">
|
||||||
<Statusbar
|
<OfflineBanner
|
||||||
online={online}
|
showsHeader={false}
|
||||||
lastConnectTime={lastConnectTime}
|
streamName={name}
|
||||||
lastDisconnectTime={lastDisconnectTime}
|
customText={offlineMessage}
|
||||||
viewerCount={viewerCount}
|
notificationsEnabled={supportsBrowserNotifications}
|
||||||
className={classnames(styles.topSectionElement, styles.statusBar)}
|
fediverseAccount={fediverseAccount}
|
||||||
|
lastLive={lastDisconnectTime}
|
||||||
|
onNotifyClick={() => setShowNotifyModal(true)}
|
||||||
|
onFollowClick={() => setShowFollowModal(true)}
|
||||||
|
className={styles.topSectionElement}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col span={24} style={{ paddingRight: dynamicPadding }}>
|
||||||
|
{isStreamLive && (
|
||||||
|
<Statusbar
|
||||||
|
online={online}
|
||||||
|
lastConnectTime={lastConnectTime}
|
||||||
|
lastDisconnectTime={lastDisconnectTime}
|
||||||
|
viewerCount={viewerCount}
|
||||||
|
className={classnames(styles.topSectionElement, styles.statusBar)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col span={24} style={{ paddingRight: dynamicPadding }}>
|
||||||
|
<ActionButtons
|
||||||
|
isMobile={isMobile}
|
||||||
|
supportFediverseFeatures={supportFediverseFeatures}
|
||||||
|
supportsBrowserNotifications={supportsBrowserNotifications}
|
||||||
|
showNotifyReminder={showNotifyReminder}
|
||||||
|
setShowNotifyModal={setShowNotifyModal}
|
||||||
|
disableNotifyReminderPopup={disableNotifyReminderPopup}
|
||||||
|
externalActions={externalActions}
|
||||||
|
setExternalActionToDisplay={setExternalActionToDisplay}
|
||||||
|
setShowFollowModal={setShowFollowModal}
|
||||||
|
externalActionSelected={externalActionSelected}
|
||||||
/>
|
/>
|
||||||
) : (
|
</Col>
|
||||||
<div className="statusbar-placeholder" />
|
</Row>
|
||||||
)}
|
|
||||||
<div className={styles.midSection}>
|
|
||||||
<div className={styles.buttonsLogoTitleSection}>
|
|
||||||
{!isMobile && (
|
|
||||||
<ActionButtonRow>
|
|
||||||
{externalActionButtons}
|
|
||||||
{supportFediverseFeatures && (
|
|
||||||
<FollowButton size="small" onClick={() => setShowFollowModal(true)} />
|
|
||||||
)}
|
|
||||||
{supportsBrowserNotifications && (
|
|
||||||
<NotifyReminderPopup
|
|
||||||
open={showNotifyReminder}
|
|
||||||
notificationClicked={() => setShowNotifyModal(true)}
|
|
||||||
notificationClosed={() => disableNotifyReminderPopup()}
|
|
||||||
>
|
|
||||||
<NotifyButton onClick={() => setShowNotifyModal(true)} />
|
|
||||||
</NotifyReminderPopup>
|
|
||||||
)}
|
|
||||||
</ActionButtonRow>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Browser Notifications"
|
title="Browser Notifications"
|
||||||
open={showNotifyModal}
|
open={showNotifyModal}
|
||||||
afterClose={() => disableNotifyReminderPopup()}
|
afterClose={() => disableNotifyReminderPopup()}
|
||||||
handleCancel={() => disableNotifyReminderPopup()}
|
handleCancel={() => disableNotifyReminderPopup()}
|
||||||
>
|
>
|
||||||
<BrowserNotifyModal />
|
<BrowserNotifyModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
<Row>
|
||||||
</div>
|
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<MobileContent
|
<MobileContent
|
||||||
name={name}
|
name={name}
|
||||||
@ -284,32 +265,25 @@ export const Content: FC = () => {
|
|||||||
messages={messages}
|
messages={messages}
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
showChat={showChat}
|
showChat={showChat}
|
||||||
actions={externalActions}
|
|
||||||
setExternalActionToDisplay={externalActionSelected}
|
|
||||||
setShowNotifyPopup={setShowNotifyModal}
|
|
||||||
setShowFollowModal={setShowFollowModal}
|
setShowFollowModal={setShowFollowModal}
|
||||||
supportFediverseFeatures={supportFediverseFeatures}
|
supportFediverseFeatures={supportFediverseFeatures}
|
||||||
supportsBrowserNotifications={supportsBrowserNotifications}
|
|
||||||
notifyItemSelected={() => setShowNotifyModal(true)}
|
|
||||||
followItemSelected={() => setShowFollowModal(true)}
|
|
||||||
externalActionSelected={externalActionSelected}
|
|
||||||
chatEnabled={isChatAvailable}
|
chatEnabled={isChatAvailable}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DesktopContent
|
<Col span={24} style={{ paddingRight: dynamicPadding }}>
|
||||||
name={name}
|
<DesktopContent
|
||||||
summary={summary}
|
name={name}
|
||||||
tags={tags}
|
summary={summary}
|
||||||
socialHandles={socialHandles}
|
tags={tags}
|
||||||
extraPageContent={extraPageContent}
|
socialHandles={socialHandles}
|
||||||
setShowFollowModal={setShowFollowModal}
|
extraPageContent={extraPageContent}
|
||||||
supportFediverseFeatures={supportFediverseFeatures}
|
setShowFollowModal={setShowFollowModal}
|
||||||
/>
|
supportFediverseFeatures={supportFediverseFeatures}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
)}
|
)}
|
||||||
{!isMobile && <Footer version={version} />}
|
</Row>
|
||||||
</div>
|
</>
|
||||||
{showChat && !isMobile && <Sidebar />}
|
|
||||||
</div>
|
|
||||||
{externalActionToDisplay && (
|
{externalActionToDisplay && (
|
||||||
<ExternalModal
|
<ExternalModal
|
||||||
externalActionToDisplay={externalActionToDisplay}
|
externalActionToDisplay={externalActionToDisplay}
|
||||||
|
@ -70,7 +70,7 @@ export const DesktopContent: FC<DesktopContentProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={styles.lowerHalf} id="skip-to-content">
|
<div id="skip-to-content">
|
||||||
<ContentHeader
|
<ContentHeader
|
||||||
name={name}
|
name={name}
|
||||||
summary={summary}
|
summary={summary}
|
||||||
@ -80,7 +80,7 @@ export const DesktopContent: FC<DesktopContentProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.lowerSection}>
|
<div>
|
||||||
{items.length > 1 ? (
|
{items.length > 1 ? (
|
||||||
<Tabs defaultActiveKey="0" items={items} />
|
<Tabs defaultActiveKey="0" items={items} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -4,13 +4,10 @@ import { Skeleton, TabsProps } from 'antd';
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { SocialLink } from '../../../interfaces/social-link.model';
|
import { SocialLink } from '../../../interfaces/social-link.model';
|
||||||
import styles from './Content.module.scss';
|
import styles from './Content.module.scss';
|
||||||
import mobileStyles from './MobileContent.module.scss';
|
|
||||||
import { CustomPageContent } from '../CustomPageContent/CustomPageContent';
|
import { CustomPageContent } from '../CustomPageContent/CustomPageContent';
|
||||||
import { ContentHeader } from '../../common/ContentHeader/ContentHeader';
|
import { ContentHeader } from '../../common/ContentHeader/ContentHeader';
|
||||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||||
import { CurrentUser } from '../../../interfaces/current-user';
|
import { CurrentUser } from '../../../interfaces/current-user';
|
||||||
import { ActionButtonMenu } from '../../action-buttons/ActionButtonMenu/ActionButtonMenu';
|
|
||||||
import { ExternalAction } from '../../../interfaces/external-action';
|
|
||||||
import { ComponentError } from '../ComponentError/ComponentError';
|
import { ComponentError } from '../ComponentError/ComponentError';
|
||||||
|
|
||||||
export type MobileContentProps = {
|
export type MobileContentProps = {
|
||||||
@ -19,19 +16,12 @@ export type MobileContentProps = {
|
|||||||
tags: string[];
|
tags: string[];
|
||||||
socialHandles: SocialLink[];
|
socialHandles: SocialLink[];
|
||||||
extraPageContent: string;
|
extraPageContent: string;
|
||||||
notifyItemSelected: () => void;
|
|
||||||
followItemSelected: () => void;
|
|
||||||
setExternalActionToDisplay: (action: ExternalAction) => void;
|
|
||||||
setShowNotifyPopup: (show: boolean) => void;
|
|
||||||
setShowFollowModal: (show: boolean) => void;
|
setShowFollowModal: (show: boolean) => void;
|
||||||
supportFediverseFeatures: boolean;
|
supportFediverseFeatures: boolean;
|
||||||
messages: ChatMessage[];
|
messages: ChatMessage[];
|
||||||
currentUser: CurrentUser;
|
currentUser: CurrentUser;
|
||||||
showChat: boolean;
|
showChat: boolean;
|
||||||
chatEnabled: boolean;
|
chatEnabled: boolean;
|
||||||
actions: ExternalAction[];
|
|
||||||
externalActionSelected: (action: ExternalAction) => void;
|
|
||||||
supportsBrowserNotifications: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// lazy loaded components
|
// lazy loaded components
|
||||||
@ -88,25 +78,6 @@ const ChatContent: FC<ChatContentProps> = ({ showChat, chatEnabled, messages, cu
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionButton = ({
|
|
||||||
supportFediverseFeatures,
|
|
||||||
supportsBrowserNotifications,
|
|
||||||
actions,
|
|
||||||
setExternalActionToDisplay,
|
|
||||||
setShowNotifyPopup,
|
|
||||||
setShowFollowModal,
|
|
||||||
}) => (
|
|
||||||
<ActionButtonMenu
|
|
||||||
className={styles.actionButtonMenu}
|
|
||||||
showFollowItem={supportFediverseFeatures}
|
|
||||||
showNotifyItem={supportsBrowserNotifications}
|
|
||||||
actions={actions}
|
|
||||||
externalActionSelected={setExternalActionToDisplay}
|
|
||||||
notifyItemSelected={() => setShowNotifyPopup(true)}
|
|
||||||
followItemSelected={() => setShowFollowModal(true)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const MobileContent: FC<MobileContentProps> = ({
|
export const MobileContent: FC<MobileContentProps> = ({
|
||||||
name,
|
name,
|
||||||
summary,
|
summary,
|
||||||
@ -117,12 +88,8 @@ export const MobileContent: FC<MobileContentProps> = ({
|
|||||||
currentUser,
|
currentUser,
|
||||||
showChat,
|
showChat,
|
||||||
chatEnabled,
|
chatEnabled,
|
||||||
actions,
|
|
||||||
setExternalActionToDisplay,
|
|
||||||
setShowNotifyPopup,
|
|
||||||
setShowFollowModal,
|
setShowFollowModal,
|
||||||
supportFediverseFeatures,
|
supportFediverseFeatures,
|
||||||
supportsBrowserNotifications,
|
|
||||||
}) => {
|
}) => {
|
||||||
const aboutTabContent = (
|
const aboutTabContent = (
|
||||||
<>
|
<>
|
||||||
@ -160,20 +127,6 @@ export const MobileContent: FC<MobileContentProps> = ({
|
|||||||
items.push({ label: 'Followers', key: '3', children: followersTabContent });
|
items.push({ label: 'Followers', key: '3', children: followersTabContent });
|
||||||
}
|
}
|
||||||
|
|
||||||
const replacementTabBar = (props, DefaultTabBar) => (
|
|
||||||
<div className={styles.replacementBar}>
|
|
||||||
<DefaultTabBar {...props} className={styles.defaultTabBar} />
|
|
||||||
<ActionButton
|
|
||||||
supportFediverseFeatures={supportFediverseFeatures}
|
|
||||||
supportsBrowserNotifications={supportsBrowserNotifications}
|
|
||||||
actions={actions}
|
|
||||||
setExternalActionToDisplay={setExternalActionToDisplay}
|
|
||||||
setShowNotifyPopup={setShowNotifyPopup}
|
|
||||||
setShowFollowModal={setShowFollowModal}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
@ -182,29 +135,9 @@ export const MobileContent: FC<MobileContentProps> = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={styles.lowerSectionMobile}>
|
<div className={styles.lowerSectionMobile}>
|
||||||
{items.length > 1 ? (
|
{items.length > 1 && <Tabs defaultActiveKey="0" items={items} />}
|
||||||
<Tabs
|
|
||||||
className={styles.tabs}
|
|
||||||
defaultActiveKey="0"
|
|
||||||
items={items}
|
|
||||||
renderTabBar={replacementTabBar}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className={mobileStyles.noTabsActionMenuButton}>
|
|
||||||
<ActionButton
|
|
||||||
supportFediverseFeatures={supportFediverseFeatures}
|
|
||||||
supportsBrowserNotifications={supportsBrowserNotifications}
|
|
||||||
actions={actions}
|
|
||||||
setExternalActionToDisplay={setExternalActionToDisplay}
|
|
||||||
setShowNotifyPopup={setShowNotifyPopup}
|
|
||||||
setShowFollowModal={setShowFollowModal}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={mobileStyles.noTabsAboutContent}>{aboutTabContent}</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.mobileNoTabs}>{items.length <= 1 && aboutTabContent}</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@import '../../../styles/mixins.scss';
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
display: none;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
height: var(--footer-height);
|
height: var(--footer-height);
|
||||||
@ -16,8 +16,17 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-top: 1px solid rgba(214, 211, 211, 0.5);
|
border-top: 1px solid rgba(214, 211, 211, 0.5);
|
||||||
|
|
||||||
|
@include screen(mobile) {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include screen(desktop) {
|
@include screen(desktop) {
|
||||||
|
@ -9,14 +9,20 @@
|
|||||||
padding: 0.7rem var(--content-padding);
|
padding: 0.7rem var(--content-padding);
|
||||||
box-shadow: 0px 1px 3px 1px rgb(0 0 0 / 10%);
|
box-shadow: 0px 1px 3px 1px rgb(0 0 0 / 10%);
|
||||||
background-color: var(--theme-color-background-header);
|
background-color: var(--theme-color-background-header);
|
||||||
|
position: fixed;
|
||||||
|
top:0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 200;
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: unset;
|
margin-top: unset;
|
||||||
margin-bottom: unset;
|
margin-bottom: unset;
|
||||||
}
|
}
|
||||||
|
// fixes header on tablet and below
|
||||||
@include screen(mobile) {
|
@include screen(tablet) {
|
||||||
--header-height: 3.85rem;
|
--header-height: 3.85rem;
|
||||||
|
position: fixed;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,15 @@
|
|||||||
.outerContainer {
|
.outerContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@include screen(tablet) {
|
||||||
|
height: 430px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screen (mobile) {
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.innerContainer {
|
.innerContainer {
|
||||||
@ -11,6 +20,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 3rem auto;
|
margin: 3rem auto;
|
||||||
padding: 2.4em;
|
padding: 2.4em;
|
||||||
|
color: var(--color-owncast-palette-3);
|
||||||
|
|
||||||
@include screen(tablet) {
|
@include screen(tablet) {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
|
@ -2,10 +2,16 @@
|
|||||||
|
|
||||||
.root {
|
.root {
|
||||||
background-color: var(--theme-color-components-chat-background);
|
background-color: var(--theme-color-components-chat-background);
|
||||||
display: none;
|
|
||||||
@include screen(desktop) {
|
@include screen(desktop) {
|
||||||
position: sticky;
|
position: fixed;
|
||||||
display: block;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 69px;
|
||||||
|
z-index:100;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1120px) and (min-width: 768px) {
|
||||||
|
top: 65px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,4 +25,5 @@ Only way to target it apparently
|
|||||||
-moz-box-flex: 1 !important;
|
-moz-box-flex: 1 !important;
|
||||||
flex-grow: 1 !important;
|
flex-grow: 1 !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
width: 100%!important;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export const Sidebar: FC = () => {
|
|||||||
|
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
return (
|
return (
|
||||||
<Sider className={styles.root} collapsedWidth={0} width={320}>
|
<Sider className={styles.root} collapsedWidth={0} width={100}>
|
||||||
<Spin spinning size="large" />
|
<Spin spinning size="large" />
|
||||||
</Sider>
|
</Sider>
|
||||||
);
|
);
|
||||||
|
@ -12,6 +12,23 @@
|
|||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include screen(desktop) {
|
||||||
|
// prevent sidebar from overlapping stream
|
||||||
|
// padding-right: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
//set height of player for tablet
|
||||||
|
@include screen(tablet) {
|
||||||
|
height: 400px;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
//set height of player for mobile
|
||||||
|
@include screen(mobile) {
|
||||||
|
height: 250px;
|
||||||
|
max-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
.player,
|
.player,
|
||||||
.poster {
|
.poster {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tablet will also apply to mobile as there is no cut-off for min-width, however changing this now could break CSS all over the site.
|
||||||
@if $breakpoint == tablet {
|
@if $breakpoint == tablet {
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
@content;
|
@content;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user