10 changed files with 315 additions and 4 deletions
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
import { useRecoilValue } from 'recoil'; |
||||
import { Layout, Row, Col } from 'antd'; |
||||
import { useState } from 'react'; |
||||
import { ServerStatus } from '../../models/ServerStatus'; |
||||
import { ServerStatusStore, serverStatusState } from '../stores/ServerStatusStore'; |
||||
import { ClientConfigStore, clientConfigState } from '../stores/ClientConfigStore'; |
||||
import { ClientConfig } from '../../models/ClientConfig'; |
||||
|
||||
const { Header, Content, Footer, Sider } = Layout; |
||||
|
||||
function Main() { |
||||
const serverStatus = useRecoilValue<ServerStatus>(serverStatusState); |
||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigState); |
||||
|
||||
const { name, version, extraPageContent } = clientConfig; |
||||
const [chatCollapsed, setChatCollapsed] = useState(false); |
||||
|
||||
const toggleChatCollapsed = () => { |
||||
setChatCollapsed(!chatCollapsed); |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<ServerStatusStore /> |
||||
<ClientConfigStore /> |
||||
|
||||
<Layout> |
||||
<Sider |
||||
collapsed={chatCollapsed} |
||||
width={300} |
||||
style={{ |
||||
position: 'fixed', |
||||
right: 0, |
||||
top: 0, |
||||
bottom: 0, |
||||
}} |
||||
/> |
||||
<Layout className="site-layout" style={{ marginRight: 200 }}> |
||||
<Header |
||||
className="site-layout-background" |
||||
style={{ position: 'fixed', zIndex: 1, width: '100%' }} |
||||
> |
||||
{name} |
||||
<button onClick={toggleChatCollapsed}>Toggle Chat</button> |
||||
</Header> |
||||
<Content style={{ margin: '80px 16px 0', overflow: 'initial' }}> |
||||
<div> |
||||
<Row> |
||||
<Col span={24}>Video player goes here</Col> |
||||
</Row> |
||||
<Row> |
||||
<Col span={24}> |
||||
<Content dangerouslySetInnerHTML={{ __html: extraPageContent }} /> |
||||
</Col> |
||||
</Row> |
||||
</div> |
||||
</Content> |
||||
<Footer style={{ textAlign: 'center' }}>Footer: Owncast {version}</Footer> |
||||
</Layout> |
||||
</Layout> |
||||
</> |
||||
); |
||||
// return (
|
||||
// <div>
|
||||
|
||||
// <Layout>
|
||||
// <Header className="header">
|
||||
// {name}
|
||||
// <button onClick={toggleChatCollapsed}>Toggle Chat</button>
|
||||
// </Header>
|
||||
// <Content>
|
||||
// <Layout>
|
||||
// <Row>
|
||||
// <Col span={24}>Video player goes here</Col>
|
||||
// </Row>
|
||||
// <Row>
|
||||
// <Col span={24}>
|
||||
// <Content dangerouslySetInnerHTML={{ __html: extraPageContent }} />
|
||||
// </Col>
|
||||
// </Row>
|
||||
|
||||
// <Sider collapsed={chatCollapsed} width={300}>
|
||||
// chat
|
||||
// </Sider>
|
||||
// </Layout>
|
||||
// </Content>
|
||||
// <Footer>Footer: Owncast {version}</Footer>
|
||||
// </Layout>
|
||||
// </div>
|
||||
// );
|
||||
} |
||||
|
||||
export default Main; |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
import { useEffect } from 'react'; |
||||
import { ReactElement } from 'react-markdown/lib/react-markdown'; |
||||
import { atom, useRecoilState } from 'recoil'; |
||||
import { makeEmptyClientConfig, ClientConfig } from '../../models/ClientConfig'; |
||||
import ClientConfigService from '../../services/ClientConfigService'; |
||||
|
||||
export const clientConfigState = atom({ |
||||
key: 'clientConfigState', |
||||
default: makeEmptyClientConfig(), |
||||
}); |
||||
|
||||
export function ClientConfigStore(): ReactElement { |
||||
const [, setClientConfig] = useRecoilState<ClientConfig>(clientConfigState); |
||||
|
||||
const updateClientConfig = async () => { |
||||
try { |
||||
const config = await ClientConfigService.getConfig(); |
||||
console.log(`ClientConfig: ${JSON.stringify(config)}`); |
||||
setClientConfig(config); |
||||
} catch (error) { |
||||
console.error(`ClientConfigService -> getConfig() ERROR: \n${error}`); |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
updateClientConfig(); |
||||
}, []); |
||||
|
||||
return null; |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
import { useEffect } from 'react'; |
||||
import { ReactElement } from 'react-markdown/lib/react-markdown'; |
||||
import { atom, useRecoilState } from 'recoil'; |
||||
import { ServerStatus, makeEmptyServerStatus } from '../../models/ServerStatus'; |
||||
import ServerStatusService from '../../services/StatusService'; |
||||
|
||||
export const serverStatusState = atom({ |
||||
key: 'serverStatusState', |
||||
default: makeEmptyServerStatus(), |
||||
}); |
||||
|
||||
export function ServerStatusStore(): ReactElement { |
||||
const [, setServerStatus] = useRecoilState<ServerStatus>(serverStatusState); |
||||
|
||||
const updateServerStatus = async () => { |
||||
try { |
||||
const status = await ServerStatusService.getStatus(); |
||||
setServerStatus(status); |
||||
return status; |
||||
} catch (error) { |
||||
console.error(`serverStatusState -> getStatus() ERROR: \n${error}`); |
||||
return null; |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
setInterval(() => { |
||||
updateServerStatus(); |
||||
}, 5000); |
||||
updateServerStatus(); |
||||
}, []); |
||||
|
||||
return null; |
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
export interface ClientConfig { |
||||
name: string; |
||||
summary: string; |
||||
logo: string; |
||||
tags: string[]; |
||||
version: string; |
||||
nsfw: boolean; |
||||
extraPageContent: string; |
||||
socialHandles: SocialHandle[]; |
||||
chatDisabled: boolean; |
||||
externalActions: any[]; |
||||
customStyles: string; |
||||
maxSocketPayloadSize: number; |
||||
federation: Federation; |
||||
notifications: Notifications; |
||||
authentication: Authentication; |
||||
} |
||||
|
||||
interface Authentication { |
||||
indieAuthEnabled: boolean; |
||||
} |
||||
|
||||
interface Federation { |
||||
enabled: boolean; |
||||
account: string; |
||||
followerCount: number; |
||||
} |
||||
|
||||
interface Notifications { |
||||
browser: Browser; |
||||
} |
||||
|
||||
interface Browser { |
||||
enabled: boolean; |
||||
publicKey: string; |
||||
} |
||||
|
||||
interface SocialHandle { |
||||
platform: string; |
||||
url: string; |
||||
icon: string; |
||||
} |
||||
|
||||
export function makeEmptyClientConfig(): ClientConfig { |
||||
return { |
||||
name: '', |
||||
summary: '', |
||||
logo: '', |
||||
tags: [], |
||||
version: '', |
||||
nsfw: false, |
||||
extraPageContent: '', |
||||
socialHandles: [], |
||||
chatDisabled: false, |
||||
externalActions: [], |
||||
customStyles: '', |
||||
maxSocketPayloadSize: 0, |
||||
federation: { |
||||
enabled: false, |
||||
account: '', |
||||
followerCount: 0, |
||||
}, |
||||
notifications: { |
||||
browser: { |
||||
enabled: false, |
||||
publicKey: '', |
||||
}, |
||||
}, |
||||
authentication: { |
||||
indieAuthEnabled: false, |
||||
}, |
||||
}; |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
export interface ServerStatus { |
||||
online: boolean; |
||||
viewerCount: number; |
||||
lastConnectTime?: Date; |
||||
lastDisconnectTime?: Date; |
||||
versionNumber?: string; |
||||
streamTitle?: string; |
||||
} |
||||
|
||||
export function makeEmptyServerStatus(): ServerStatus { |
||||
return { |
||||
online: false, |
||||
viewerCount: 0, |
||||
}; |
||||
} |
@ -1,8 +1,10 @@
@@ -1,8 +1,10 @@
|
||||
import { RecoilRoot } from 'recoil'; |
||||
import Main from '../components/layouts/main'; |
||||
|
||||
export default function Home() { |
||||
return ( |
||||
<div> |
||||
This is where v2 of the Owncast web UI will be built. Begin with the layout component |
||||
https://ant.design/components/layout/ and edit pages/index.tsx.
|
||||
</div> |
||||
<RecoilRoot> |
||||
<Main /> |
||||
</RecoilRoot> |
||||
); |
||||
} |
||||
|
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
import { ClientConfig } from '../models/ClientConfig'; |
||||
const ENDPOINT = `http://localhost:8080/api/config`; |
||||
|
||||
class ClientConfigService { |
||||
public static async getConfig(): Promise<ClientConfig> { |
||||
const response = await fetch(ENDPOINT); |
||||
const status = await response.json(); |
||||
return status; |
||||
} |
||||
} |
||||
|
||||
export default ClientConfigService; |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
import ServerStatus from '../models/ServerStatus'; |
||||
|
||||
const ENDPOINT = `http://localhost:8080/api/status`; |
||||
|
||||
class ServerStatusService { |
||||
public static async getStatus(): Promise<ServerStatus> { |
||||
const response = await fetch(ENDPOINT); |
||||
const status = await response.json(); |
||||
return status; |
||||
} |
||||
} |
||||
|
||||
export default ServerStatusService; |
Loading…
Reference in new issue