Browse Source

Initial api requests + stores + basic layout

pull/1886/head
Gabe Kangas 3 years ago
parent
commit
e2e21d915b
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
  1. 93
      web/components/layouts/main.tsx
  2. 30
      web/components/stores/ClientConfigStore.tsx
  3. 34
      web/components/stores/ServerStatusStore.tsx
  4. 73
      web/models/ClientConfig.ts
  5. 15
      web/models/ServerStatus.ts
  6. 38
      web/package-lock.json
  7. 1
      web/package.json
  8. 10
      web/pages/index.tsx
  9. 12
      web/services/ClientConfigService.ts
  10. 13
      web/services/StatusService.ts

93
web/components/layouts/main.tsx

@ -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;

30
web/components/stores/ClientConfigStore.tsx

@ -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;
}

34
web/components/stores/ServerStatusStore.tsx

@ -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;
}

73
web/models/ClientConfig.ts

@ -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,
},
};
}

15
web/models/ServerStatus.ts

@ -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,
};
}

38
web/package-lock.json generated

@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
"react-linkify": "1.0.0-alpha",
"react-markdown": "8.0.0",
"react-markdown-editor-lite": "1.3.2",
"recoil": "^0.7.2",
"ua-parser-js": "1.0.2"
},
"devDependencies": {
@ -18130,6 +18131,11 @@ @@ -18130,6 +18131,11 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"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": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
@ -26788,6 +26794,25 @@ @@ -26788,6 +26794,25 @@
"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": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
@ -45217,6 +45242,11 @@ @@ -45217,6 +45242,11 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"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": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
@ -51767,6 +51797,14 @@ @@ -51767,6 +51797,14 @@
"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": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",

1
web/package.json

@ -33,6 +33,7 @@ @@ -33,6 +33,7 @@
"react-linkify": "1.0.0-alpha",
"react-markdown": "8.0.0",
"react-markdown-editor-lite": "1.3.2",
"recoil": "^0.7.2",
"ua-parser-js": "1.0.2"
},
"devDependencies": {

10
web/pages/index.tsx

@ -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>
);
}

12
web/services/ClientConfigService.ts

@ -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;

13
web/services/StatusService.ts

@ -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…
Cancel
Save