Initial api requests + stores + basic layout
This commit is contained in:
93
web/components/layouts/main.tsx
Normal file
93
web/components/layouts/main.tsx
Normal file
@@ -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;
|
||||||
30
web/components/stores/ClientConfigStore.tsx
Normal file
30
web/components/stores/ClientConfigStore.tsx
Normal file
@@ -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;
|
||||||
|
}
|
||||||
34
web/components/stores/ServerStatusStore.tsx
Normal file
34
web/components/stores/ServerStatusStore.tsx
Normal file
@@ -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;
|
||||||
|
}
|
||||||
73
web/models/ClientConfig.ts
Normal file
73
web/models/ClientConfig.ts
Normal file
@@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
15
web/models/ServerStatus.ts
Normal file
15
web/models/ServerStatus.ts
Normal file
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
38
web/package-lock.json
generated
38
web/package-lock.json
generated
@@ -30,6 +30,7 @@
|
|||||||
"react-linkify": "1.0.0-alpha",
|
"react-linkify": "1.0.0-alpha",
|
||||||
"react-markdown": "8.0.0",
|
"react-markdown": "8.0.0",
|
||||||
"react-markdown-editor-lite": "1.3.2",
|
"react-markdown-editor-lite": "1.3.2",
|
||||||
|
"recoil": "^0.7.2",
|
||||||
"ua-parser-js": "1.0.2"
|
"ua-parser-js": "1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -18130,6 +18131,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/hamt_plus": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
|
||||||
|
},
|
||||||
"node_modules/handlebars": {
|
"node_modules/handlebars": {
|
||||||
"version": "4.7.7",
|
"version": "4.7.7",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
||||||
@@ -26788,6 +26794,25 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recoil": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"hamt_plus": "1.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.13.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/refractor": {
|
"node_modules/refractor": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
||||||
@@ -45217,6 +45242,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
|
||||||
},
|
},
|
||||||
|
"hamt_plus": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
|
||||||
|
},
|
||||||
"handlebars": {
|
"handlebars": {
|
||||||
"version": "4.7.7",
|
"version": "4.7.7",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
|
||||||
@@ -51767,6 +51797,14 @@
|
|||||||
"resolve": "^1.1.6"
|
"resolve": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"recoil": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-OT4pI7FOUHcIoRtjsL5Lqq+lFFzQfir4MIbUkqyJ3nqv3WfBP1pHepyurqTsK5gw+T+I2R8+uOD28yH+Lg5o4g==",
|
||||||
|
"requires": {
|
||||||
|
"hamt_plus": "1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"refractor": {
|
"refractor": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"react-linkify": "1.0.0-alpha",
|
"react-linkify": "1.0.0-alpha",
|
||||||
"react-markdown": "8.0.0",
|
"react-markdown": "8.0.0",
|
||||||
"react-markdown-editor-lite": "1.3.2",
|
"react-markdown-editor-lite": "1.3.2",
|
||||||
|
"recoil": "^0.7.2",
|
||||||
"ua-parser-js": "1.0.2"
|
"ua-parser-js": "1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { RecoilRoot } from 'recoil';
|
||||||
|
import Main from '../components/layouts/main';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<RecoilRoot>
|
||||||
This is where v2 of the Owncast web UI will be built. Begin with the layout component
|
<Main />
|
||||||
https://ant.design/components/layout/ and edit pages/index.tsx.
|
</RecoilRoot>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
12
web/services/ClientConfigService.ts
Normal file
12
web/services/ClientConfigService.ts
Normal file
@@ -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;
|
||||||
13
web/services/StatusService.ts
Normal file
13
web/services/StatusService.ts
Normal file
@@ -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;
|
||||||
Reference in New Issue
Block a user