Fix web project build errors
This commit is contained in:
@@ -4,5 +4,6 @@ interface Props {
|
||||
|
||||
export default function CustomPageContent(props: Props) {
|
||||
const { content } = props;
|
||||
// eslint-disable-next-line react/no-danger
|
||||
return <div dangerouslySetInnerHTML={{ __html: content }} />;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function PageLogo(props: Props) {
|
||||
export default function PageLogo() {
|
||||
return <div>Pimary logo component goes here</div>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { SocialLink } from '../interfaces/social-link.model';
|
||||
|
||||
interface Props {
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
links: SocialLink[];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function SocialLinks(props: Props) {
|
||||
return <div>Social links component goes here</div>;
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
width: 20px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import Modal from '../ui/Modal/Modal';
|
||||
import { ExternalAction } from '../interfaces/external-action.interface';
|
||||
import { ExternalAction } from '../../interfaces/external-action';
|
||||
import s from './ActionButton.module.scss';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||
|
||||
interface Props {
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
message: ChatMessage;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function ChatSystemMessage(props: Props) {
|
||||
return <div>Component goes here</div>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Spin } from 'antd';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||
import { ChatState } from '../../interfaces/application-state';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
interface Props {}
|
||||
|
||||
export default function ChatModerationNotification(props: Props) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||
|
||||
interface Props {
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||
|
||||
interface Props {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function ChatTextField(props: Props) {
|
||||
const [value, setValue] = useState('');
|
||||
const [showEmojis, setShowEmojis] = useState(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.root {
|
||||
height: 2rem;
|
||||
color: var(--black);
|
||||
}
|
||||
height: 2rem;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import { Button, Popover } from 'antd';
|
||||
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Transforms, createEditor, Node, BaseEditor, Text } from 'slate';
|
||||
import { Transforms, createEditor, BaseEditor, Text } from 'slate';
|
||||
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
|
||||
import EmojiPicker from './EmojiPicker';
|
||||
import WebsocketService from '../../../services/websocket-service';
|
||||
import { websocketServiceAtom } from '../../stores/ClientConfigStore';
|
||||
import { MessageType } from '../../../interfaces/socket-events';
|
||||
import s from './ChatTextField.module.scss';
|
||||
|
||||
type CustomElement = { type: 'paragraph'; children: CustomText[] };
|
||||
type CustomText = { text: string };
|
||||
@@ -25,24 +24,30 @@ interface Props {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const Image = ({ element }) => (
|
||||
<img
|
||||
// eslint-disable-next-line no-undef
|
||||
// eslint-disable-next-line react/prop-types
|
||||
src={element.url}
|
||||
alt="emoji"
|
||||
style={{ display: 'inline', position: 'relative', width: '30px', bottom: '10px' }}
|
||||
/>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const insertImage = (editor, url) => {
|
||||
const text = { text: '' };
|
||||
const image: ImageElement = { type: 'image', url, children: [text] };
|
||||
Transforms.insertNodes(editor, image);
|
||||
// const text = { text: '' };
|
||||
// const image: ImageElement = { type: 'image', url, children: [text] };
|
||||
// Transforms.insertNodes(editor, image);
|
||||
};
|
||||
|
||||
const withImages = editor => {
|
||||
const { isVoid } = editor;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
editor.isVoid = element => (element.type === 'image' ? true : isVoid(element));
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
editor.isInline = element => element.type === 'image';
|
||||
|
||||
return editor;
|
||||
@@ -52,13 +57,13 @@ export type EmptyText = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
type ImageElement = {
|
||||
type: 'image';
|
||||
url: string;
|
||||
children: EmptyText[];
|
||||
};
|
||||
// type ImageElement = {
|
||||
// type: 'image';
|
||||
// url: string;
|
||||
// children: EmptyText[];
|
||||
// };
|
||||
|
||||
const Element = props => {
|
||||
const Element = (props: any) => {
|
||||
const { attributes, children, element } = props;
|
||||
|
||||
switch (element.type) {
|
||||
@@ -71,10 +76,10 @@ const Element = props => {
|
||||
|
||||
const serialize = node => {
|
||||
if (Text.isText(node)) {
|
||||
let string = node.text;
|
||||
if (node.bold) {
|
||||
string = `<strong>${string}</strong>`;
|
||||
}
|
||||
const string = node.text;
|
||||
// if (node.bold) {
|
||||
// string = `<strong>${string}</strong>`;
|
||||
// }
|
||||
return string;
|
||||
}
|
||||
|
||||
@@ -90,8 +95,9 @@ const serialize = node => {
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function ChatTextField(props: Props) {
|
||||
const { value: originalValue } = props;
|
||||
// const { value: originalValue } = props;
|
||||
const [showEmojis, setShowEmojis] = useState(false);
|
||||
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
|
||||
const [editor] = useState(() => withImages(withReact(createEditor())));
|
||||
@@ -113,7 +119,7 @@ export default function ChatTextField(props: Props) {
|
||||
Transforms.delete(editor);
|
||||
};
|
||||
|
||||
const handleChange = e => {};
|
||||
const handleChange = () => {};
|
||||
|
||||
const handleEmojiSelect = emoji => {
|
||||
console.log(emoji);
|
||||
@@ -135,19 +141,12 @@ export default function ChatTextField(props: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const initialValue = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: originalValue }],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Slate editor={editor} value={initialValue} onChange={handleChange}>
|
||||
<Slate editor={editor} value={[]} onChange={handleChange}>
|
||||
<Editable
|
||||
onKeyDown={onKeyDown}
|
||||
renderElement={props => <Element {...props} />}
|
||||
renderElement={p => <Element {...p} />}
|
||||
placeholder="Chat message goes here..."
|
||||
/>
|
||||
</Slate>
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import data from '@emoji-mart/data';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
// import data from '@emoji-mart/data';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
export default function EmojiPicker(props) {
|
||||
interface Props {
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
onEmojiSelect: (emoji: string) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export default function EmojiPicker(props: Props) {
|
||||
const ref = useRef();
|
||||
|
||||
// TODO: Pull this custom emoji data in from the emoji API.
|
||||
const custom = [
|
||||
{
|
||||
emojis: [
|
||||
{
|
||||
id: 'party_parrot',
|
||||
name: 'Party Parrot',
|
||||
keywords: ['dance', 'dancing'],
|
||||
skins: [{ src: 'https://watch.owncast.online/img/emoji/bluntparrot.gif' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// const custom = [
|
||||
// {
|
||||
// emojis: [
|
||||
// {
|
||||
// id: 'party_parrot',
|
||||
// name: 'Party Parrot',
|
||||
// keywords: ['dance', 'dancing'],
|
||||
// skins: [{ src: 'https://watch.owncast.online/img/emoji/bluntparrot.gif' }],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ];
|
||||
|
||||
// TODO: Fix the emoji picker from throwing errors.
|
||||
// useEffect(() => {
|
||||
|
||||
@@ -7,7 +7,9 @@ interface Props {
|
||||
|
||||
export default function ChatUserMessage(props: Props) {
|
||||
const { message, showModeratorMenu } = props;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { body, user, timestamp } = message;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { displayName, displayColor } = user;
|
||||
|
||||
// TODO: Convert displayColor (a hue) to a usable color.
|
||||
|
||||
@@ -101,45 +101,6 @@ export function convertToText(str = '') {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
You would call this when a user pastes from
|
||||
the clipboard into a `contenteditable` area.
|
||||
*/
|
||||
export function convertOnPaste(event = { preventDefault() {} }, emojiList) {
|
||||
// Prevent paste.
|
||||
event.preventDefault();
|
||||
|
||||
// Set later.
|
||||
let value = '';
|
||||
|
||||
// Does method exist?
|
||||
const hasEventClipboard = !!(
|
||||
event.clipboardData &&
|
||||
typeof event.clipboardData === 'object' &&
|
||||
typeof event.clipboardData.getData === 'function'
|
||||
);
|
||||
|
||||
// Get clipboard data?
|
||||
if (hasEventClipboard) {
|
||||
value = event.clipboardData.getData('text/plain');
|
||||
}
|
||||
|
||||
// Insert into temp `<textarea>`, read back out.
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.innerHTML = value;
|
||||
value = textarea.innerText;
|
||||
|
||||
// Clean up text.
|
||||
value = convertToText(value);
|
||||
|
||||
const HTML = emojify(value, emojiList);
|
||||
|
||||
// Insert text.
|
||||
if (typeof document.execCommand === 'function') {
|
||||
document.execCommand('insertHTML', false, HTML);
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmojiMarkup(data, isCustom) {
|
||||
const emojiUrl = isCustom ? data.emoji : data.url;
|
||||
const emojiName = (
|
||||
@@ -156,6 +117,7 @@ export function trimNbsp(html) {
|
||||
export function emojify(HTML, emojiList) {
|
||||
const textValue = convertToText(HTML);
|
||||
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let lastPos = textValue.length; lastPos >= 0; lastPos--) {
|
||||
const endPos = textValue.lastIndexOf(':', lastPos);
|
||||
if (endPos <= 0) {
|
||||
@@ -170,8 +132,9 @@ export function emojify(HTML, emojiList) {
|
||||
emojiItem => emojiItem.name.toLowerCase() === typedEmoji.toLowerCase(),
|
||||
);
|
||||
|
||||
if (emojiIndex != -1) {
|
||||
if (emojiIndex !== -1) {
|
||||
const emojiImgElement = createEmojiMarkup(emojiList[emojiIndex], true);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
HTML = HTML.replace(`:${typedEmoji}:`, emojiImgElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import s from './Logo.module.scss'
|
||||
import s from './Logo.module.scss';
|
||||
|
||||
interface Props {
|
||||
variant: 'simple' | 'contrast'
|
||||
variant: 'simple' | 'contrast';
|
||||
}
|
||||
|
||||
export default function Logo({variant = 'simple'}: Props) {
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
[s.simple]: variant === 'simple',
|
||||
[s.contrast]: variant === 'contrast',
|
||||
}
|
||||
)
|
||||
export default function Logo({ variant = 'simple' }: Props) {
|
||||
const rootClassName = cn(s.root, {
|
||||
[s.simple]: variant === 'simple',
|
||||
[s.contrast]: variant === 'contrast',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={rootClassName}>
|
||||
|
||||
@@ -6,8 +6,8 @@ import { ChatState, ChatVisibilityState } from '../../../interfaces/application-
|
||||
import s from './UserDropdown.module.scss';
|
||||
|
||||
interface Props {
|
||||
username?: string;
|
||||
chatState?: ChatState;
|
||||
username: string;
|
||||
chatState: ChatState;
|
||||
}
|
||||
|
||||
export default function UserDropdown({ username = 'test-user', chatState }: Props) {
|
||||
@@ -44,11 +44,6 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
{/*
|
||||
<button type="button" className="ant-dropdown-link" onClick={e => e.preventDefault()}>
|
||||
{username} <DownOutlined />
|
||||
</button>
|
||||
*/}
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from './UserDropdown'
|
||||
export { default } from './UserDropdown';
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as UserDropdown } from './UserDropdown'
|
||||
export { default as OwncastLogo } from './Logo'
|
||||
export { default as UserDropdown } from './UserDropdown';
|
||||
export { default as OwncastLogo } from './Logo';
|
||||
|
||||
18
web/components/layouts/admin-layout.tsx
Normal file
18
web/components/layouts/admin-layout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { AppProps } from 'next/app';
|
||||
import ServerStatusProvider from '../../utils/server-status-context';
|
||||
import AlertMessageProvider from '../../utils/alert-message-context';
|
||||
import MainLayout from '../main-layout';
|
||||
|
||||
function AdminLayout({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<ServerStatusProvider>
|
||||
<AlertMessageProvider>
|
||||
<MainLayout>
|
||||
<Component {...pageProps} />
|
||||
</MainLayout>
|
||||
</AlertMessageProvider>
|
||||
</ServerStatusProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminLayout;
|
||||
@@ -130,7 +130,7 @@ export default function MainLayout(props) {
|
||||
<Sider width={240} className="side-nav">
|
||||
<h1 className="owncast-title">
|
||||
<span className="logo-container">
|
||||
<OwncastLogo />
|
||||
<OwncastLogo variant="simple" />
|
||||
</span>
|
||||
<span className="title-label">Owncast Admin</span>
|
||||
</h1>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
interface Props {}
|
||||
|
||||
export default function AuthModal(props: Props) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
interface Props {}
|
||||
|
||||
export default function BrowserNotifyModal(props: Props) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
interface Props {}
|
||||
|
||||
export default function FediAuthModal(props: Props) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
interface Props {}
|
||||
|
||||
export default function FollowModal(props: Props) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
interface Props {}
|
||||
|
||||
export default function IndieAuthModal(props: Props) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Card, Col, Row, Typography } from 'antd';
|
||||
import Link from 'next/link';
|
||||
import { useContext } from 'react';
|
||||
import LogTable from './log-table';
|
||||
import OwncastLogo from './logo';
|
||||
import OwncastLogo from './common/Logo/Logo';
|
||||
import NewsFeed from './news-feed';
|
||||
import { ConfigDetails } from '../types/config-section';
|
||||
import { ServerStatusContext } from '../utils/server-status-context';
|
||||
@@ -125,7 +125,7 @@ export default function Offline({ logs = [], config }: OfflineProps) {
|
||||
<Col span={12} offset={6}>
|
||||
<div className="offline-intro">
|
||||
<span className="logo">
|
||||
<OwncastLogo />
|
||||
<OwncastLogo variant="simple" />
|
||||
</span>
|
||||
<div>
|
||||
<Title level={2}>No stream is active</Title>
|
||||
|
||||
@@ -6,7 +6,6 @@ import ClientConfigService from '../../services/client-config-service';
|
||||
import ChatService from '../../services/chat-service';
|
||||
import WebsocketService from '../../services/websocket-service';
|
||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||
import { getLocalStorage, setLocalStorage } from '../../utils/helpers';
|
||||
import {
|
||||
AppState,
|
||||
ChatState,
|
||||
@@ -16,10 +15,10 @@ import {
|
||||
getChatVisibilityState,
|
||||
} from '../../interfaces/application-state';
|
||||
import {
|
||||
SocketEvent,
|
||||
ConnectedClientInfoEvent,
|
||||
MessageType,
|
||||
ChatEvent,
|
||||
SocketEvent,
|
||||
} from '../../interfaces/socket-events';
|
||||
import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo';
|
||||
import handleChatMessage from './eventhandlers/handleChatMessage';
|
||||
@@ -77,10 +76,8 @@ export function ClientConfigStore() {
|
||||
const [chatMessages, setChatMessages] = useRecoilState<ChatMessage[]>(chatMessagesAtom);
|
||||
const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom);
|
||||
const [appState, setAppState] = useRecoilState<AppState>(appStateAtom);
|
||||
const [videoState, setVideoState] = useRecoilState<VideoState>(videoStateAtom);
|
||||
const [accessToken, setAccessToken] = useRecoilState<string>(accessTokenAtom);
|
||||
const [websocketService, setWebsocketService] =
|
||||
useRecoilState<WebsocketService>(websocketServiceAtom);
|
||||
const setWebsocketService = useSetRecoilState<WebsocketService>(websocketServiceAtom);
|
||||
|
||||
let ws: WebsocketService;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConnectedClientInfoEvent, SocketEvent } from '../../../interfaces/socket-events';
|
||||
import { ConnectedClientInfoEvent } from '../../../interfaces/socket-events';
|
||||
|
||||
export default function handleConnectedClientInfoMessage(message: ConnectedClientInfoEvent) {
|
||||
console.log('connected client', message);
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
.root {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mobileChat {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height: calc(50vh - var(--header-h));
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height: calc(50vh - var(--header-h));
|
||||
}
|
||||
|
||||
.leftCol {
|
||||
display: grid;
|
||||
// -64px, which is the header
|
||||
grid-template-rows: 50vh calc(50vh - var(--header-h));
|
||||
display: grid;
|
||||
// -64px, which is the header
|
||||
grid-template-rows: 50vh calc(50vh - var(--header-h));
|
||||
}
|
||||
.lowerRow {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr var(--header-h);
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr var(--header-h);
|
||||
}
|
||||
|
||||
.pageContentSection {
|
||||
@@ -32,10 +32,10 @@
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.mobileChat {
|
||||
display: none;
|
||||
}
|
||||
.root[data-columns='2'] {
|
||||
grid-template-columns: 1fr var(--chat-w);
|
||||
}
|
||||
.mobileChat {
|
||||
display: none;
|
||||
}
|
||||
.root[data-columns='2'] {
|
||||
grid-template-columns: 1fr var(--chat-w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Layout, Button, Col, Tabs } from 'antd';
|
||||
import Grid from 'antd/lib/card/Grid';
|
||||
import { Layout, Button, Tabs } from 'antd';
|
||||
import {
|
||||
chatVisibilityAtom,
|
||||
clientConfigStateAtom,
|
||||
@@ -23,6 +22,7 @@ import ActionButtonRow from '../../action-buttons/ActionButtonRow';
|
||||
import ActionButton from '../../action-buttons/ActionButton';
|
||||
import Statusbar from '../Statusbar/Statusbar';
|
||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||
import { Follower } from '../../../interfaces/follower';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
const { Content } = Layout;
|
||||
@@ -34,8 +34,8 @@ export default function ContentComponent() {
|
||||
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
|
||||
const chatState = useRecoilValue<ChatState>(chatStateAtom);
|
||||
|
||||
const { extraPageContent } = clientConfig;
|
||||
const { online, viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } = status;
|
||||
const { extraPageContent, version } = clientConfig;
|
||||
const { online, viewerCount, lastConnectTime, lastDisconnectTime } = status;
|
||||
|
||||
const followers: Follower[] = [];
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function ContentComponent() {
|
||||
<ChatTextField />
|
||||
</div>
|
||||
)}
|
||||
<Footer />
|
||||
<Footer version={version} />
|
||||
</div>
|
||||
</div>
|
||||
{chatOpen && <Sidebar />}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from "./Content"
|
||||
export { default } from './Content';
|
||||
|
||||
@@ -54,11 +54,11 @@ export default function CrossfadeImage({
|
||||
return (
|
||||
<span style={spanStyle}>
|
||||
{[...srcs, nextSrc].map(
|
||||
(src, index) =>
|
||||
src !== '' && (
|
||||
(singleSrc, index) =>
|
||||
singleSrc !== '' && (
|
||||
<img
|
||||
key={(key + index) % 3}
|
||||
src={src}
|
||||
key={singleSrc}
|
||||
src={singleSrc}
|
||||
alt=""
|
||||
style={imgStyles[index]}
|
||||
onLoad={index === 2 ? onLoadImg : undefined}
|
||||
|
||||
@@ -2,7 +2,11 @@ import { Layout } from 'antd';
|
||||
|
||||
const { Footer } = Layout;
|
||||
|
||||
export default function FooterComponent(props) {
|
||||
interface Props {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export default function FooterComponent(props: Props) {
|
||||
const { version } = props;
|
||||
|
||||
return <Footer style={{ textAlign: 'center', height: '64px' }}>Footer: Owncast {version}</Footer>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
padding: .5rem 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Layout } from 'antd';
|
||||
import { ChatState } from '../../../interfaces/application-state';
|
||||
import { OwncastLogo, UserDropdown } from '../../common';
|
||||
import s from './Header.module.scss';
|
||||
|
||||
@@ -12,10 +13,10 @@ export default function HeaderComponent({ name = 'Your stream title' }: Props) {
|
||||
return (
|
||||
<Header className={`${s.header}`}>
|
||||
<div className={`${s.logo}`}>
|
||||
<OwncastLogo variant='contrast'/>
|
||||
<OwncastLogo variant="contrast" />
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
<UserDropdown />
|
||||
<UserDropdown username="fillmein" chatState={ChatState.Available} />
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
display: block;
|
||||
height: 100%;
|
||||
padding: 2vw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ export default function Modal(props: Props) {
|
||||
width="100%"
|
||||
height="100%"
|
||||
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
|
||||
allowpaymentrequest="true"
|
||||
frameBorder="0"
|
||||
allowFullScreen
|
||||
onLoad={() => setLoading(false)}
|
||||
|
||||
@@ -8,5 +8,3 @@
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function Statusbar(props: Props) {
|
||||
const duration = makeDurationString(new Date(lastConnectTime));
|
||||
onlineMessage = online ? `Live for ${duration}` : 'Offline';
|
||||
rightSideMessage = `${viewerCount > 0 ? `${viewerCount}` : 'No'} ${
|
||||
viewerCount == 1 ? 'viewer' : 'viewers'
|
||||
viewerCount === 1 ? 'viewer' : 'viewers'
|
||||
}`;
|
||||
} else {
|
||||
onlineMessage = 'Offline';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { default as Header } from './Header/index'
|
||||
export { default as Sidebar } from './Sidebar/index'
|
||||
export { default as Footer } from './Footer/index'
|
||||
export { default as Content } from './Content/index'
|
||||
export { default as Header } from './Header/index';
|
||||
export { default as Sidebar } from './Sidebar/index';
|
||||
export { default as Footer } from './Footer/index';
|
||||
export { default as Content } from './Content/index';
|
||||
|
||||
@@ -107,11 +107,7 @@ export default function OwncastPlayer(props: Props) {
|
||||
<div style={{ display: 'grid' }}>
|
||||
{online && (
|
||||
<div style={{ gridColumn: 1, gridRow: 1 }}>
|
||||
<VideoJS
|
||||
style={{ gridColumn: 1, gridRow: 1 }}
|
||||
options={videoJsOptions}
|
||||
onReady={handlePlayerReady}
|
||||
/>
|
||||
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
|
||||
</div>
|
||||
)}
|
||||
<div style={{ gridColumn: 1, gridRow: 1 }}>
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
.vjs-big-play-centered .vjs-big-play-button {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
background-color: black;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@ require('video.js/dist/video-js.css');
|
||||
// TODO: Restore volume that was saved in local storage.
|
||||
// import { getLocalStorage, setLocalStorage } from '../../utils/helpers.js';
|
||||
// import { PLAYER_VOLUME, URL_STREAM } from '../../utils/constants.js';
|
||||
interface Props {
|
||||
options: any;
|
||||
onReady: (player: videojs.Player) => void;
|
||||
}
|
||||
|
||||
export function VideoJS(props) {
|
||||
export function VideoJS(props: Props) {
|
||||
const videoRef = React.useRef(null);
|
||||
const playerRef = React.useRef(null);
|
||||
const { options, onReady } = props;
|
||||
@@ -18,11 +22,10 @@ export function VideoJS(props) {
|
||||
if (!playerRef.current) {
|
||||
const videoElement = videoRef.current;
|
||||
|
||||
// if (!videoElement) return;
|
||||
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
const player = (playerRef.current = videojs(videoElement, options, () => {
|
||||
player.log('player is ready');
|
||||
onReady && onReady(player);
|
||||
return onReady && onReady(player);
|
||||
}));
|
||||
|
||||
// TODO: Add airplay support, video settings menu, latency compensator, etc.
|
||||
@@ -48,6 +51,7 @@ export function VideoJS(props) {
|
||||
|
||||
return (
|
||||
<div data-vjs-player>
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||
<video ref={videoRef} className={`video-js vjs-big-play-centered ${s.player}`} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user