More blank components and stories to be filled in
This commit is contained in:
@@ -18,6 +18,15 @@ Make sure you're running an instance of Owncast on localhost:8080, as your copy
|
|||||||
|
|
||||||
```npm run dev```
|
```npm run dev```
|
||||||
|
|
||||||
|
### Components and Styles
|
||||||
|
|
||||||
|
You can start the [Storybook](https://storybook.js.org/) UI for exploring, testing, and developing components by running:
|
||||||
|
|
||||||
|
```npm run storybook```
|
||||||
|
|
||||||
|
This allows for components to be made available without the need of the server to be running and changes to be made in
|
||||||
|
isolation.
|
||||||
|
|
||||||
### Update the project
|
### Update the project
|
||||||
|
|
||||||
You can add or edit a pages by modifying `pages/something.js`. The page auto-updates as you edit the file.
|
You can add or edit a pages by modifying `pages/something.js`. The page auto-updates as you edit the file.
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ interface Props {
|
|||||||
|
|
||||||
export default function CustomPageContent(props: Props) {
|
export default function CustomPageContent(props: Props) {
|
||||||
const { content } = props;
|
const { content } = props;
|
||||||
return <div>{content}</div>;
|
return <div dangerouslySetInnerHTML={{ __html: content }} />;
|
||||||
}
|
}
|
||||||
|
|||||||
9
web/components/Follower.tsx
Normal file
9
web/components/Follower.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Follower } from '../interfaces/follower';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
follower: Follower;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FollowerCollection(props: Props) {
|
||||||
|
return <div>This is a single follower</div>;
|
||||||
|
}
|
||||||
9
web/components/FollowersCollection.tsx
Normal file
9
web/components/FollowersCollection.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Follower } from '../interfaces/follower';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
followers: Follower[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FollowerCollection(props: Props) {
|
||||||
|
return <div>List of followers go here</div>;
|
||||||
|
}
|
||||||
@@ -5,5 +5,5 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatContainer(props: Props) {
|
export default function ChatContainer(props: Props) {
|
||||||
return <div>Component goes here</div>;
|
return <div>Chat container goes here</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ReactElement } from 'react-markdown/lib/react-markdown';
|
|||||||
import { atom, useRecoilState } from 'recoil';
|
import { atom, useRecoilState } from 'recoil';
|
||||||
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
|
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
|
||||||
import ClientConfigService from '../../services/client-config-service';
|
import ClientConfigService from '../../services/client-config-service';
|
||||||
|
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||||
|
|
||||||
// The config that comes from the API.
|
// The config that comes from the API.
|
||||||
export const clientConfigState = atom({
|
export const clientConfigState = atom({
|
||||||
@@ -15,11 +16,16 @@ export const chatCurrentlyVisible = atom({
|
|||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const chatDislayName = atom({
|
export const chatDisplayName = atom({
|
||||||
key: 'chatDisplayName',
|
key: 'chatDisplayName',
|
||||||
default: '',
|
default: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const chatMessages = atom({
|
||||||
|
key: 'chatMessages',
|
||||||
|
default: [] as ChatMessage[],
|
||||||
|
});
|
||||||
|
|
||||||
export function ClientConfigStore(): ReactElement {
|
export function ClientConfigStore(): ReactElement {
|
||||||
const [, setClientConfig] = useRecoilState<ClientConfig>(clientConfigState);
|
const [, setClientConfig] = useRecoilState<ClientConfig>(clientConfigState);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { Layout, Row, Col, Tabs } from 'antd';
|
||||||
import { clientConfigState } from '../../stores/ClientConfigStore';
|
import { clientConfigState } from '../../stores/ClientConfigStore';
|
||||||
import { ClientConfig } from '../../../interfaces/client-config.model';
|
import { ClientConfig } from '../../../interfaces/client-config.model';
|
||||||
import { Layout, Row, Col } from 'antd';
|
import CustomPageContent from '../../CustomPageContent';
|
||||||
|
import OwncastPlayer from '../../video/OwncastPlayer';
|
||||||
|
import FollowerCollection from '../../FollowersCollection';
|
||||||
|
|
||||||
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
@@ -13,11 +18,20 @@ export default function FooterComponent() {
|
|||||||
<Content style={{ margin: '80px 16px 0', overflow: 'initial' }}>
|
<Content style={{ margin: '80px 16px 0', overflow: 'initial' }}>
|
||||||
<div>
|
<div>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={24}>Video player goes here</Col>
|
<Col span={24}>
|
||||||
|
<OwncastPlayer source="https://watch.owncast.online" />
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Content dangerouslySetInnerHTML={{ __html: extraPageContent }} />
|
<Tabs defaultActiveKey="1" type="card">
|
||||||
|
<TabPane tab="About" key="1">
|
||||||
|
<CustomPageContent content={extraPageContent} />
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="Followers" key="2">
|
||||||
|
<FollowerCollection />
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { clientConfigState } from '../../stores/ClientConfigStore';
|
|
||||||
import { ClientConfig } from '../../../interfaces/client-config.model';
|
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
|
|
||||||
const { Footer } = Layout;
|
const { Footer } = Layout;
|
||||||
|
|
||||||
export default function FooterComponent() {
|
export default function FooterComponent(props) {
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigState);
|
const { version } = props;
|
||||||
const { version } = clientConfig;
|
|
||||||
|
|
||||||
return <Footer style={{ textAlign: 'center' }}>Footer: Owncast {version}</Footer>;
|
return <Footer style={{ textAlign: 'center' }}>Footer: Owncast {version}</Footer>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,17 @@
|
|||||||
import s from './Header.module.scss';
|
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import { ServerStatusStore, serverStatusState } from '../../stores/ServerStatusStore';
|
import UserDropdown from '../../UserDropdownMenu';
|
||||||
import {
|
import s from './Header.module.scss';
|
||||||
ClientConfigStore,
|
|
||||||
clientConfigState,
|
|
||||||
chatCurrentlyVisible,
|
|
||||||
} from '../../stores/ClientConfigStore';
|
|
||||||
import { ClientConfig } from '../../../interfaces/client-config.model';
|
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
|
|
||||||
export default function HeaderComponent() {
|
export default function HeaderComponent(props) {
|
||||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigState);
|
const { name } = props;
|
||||||
const [chatOpen, setChatOpen] = useRecoilState(chatCurrentlyVisible);
|
|
||||||
|
|
||||||
const { name } = clientConfig;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log({ chatOpen });
|
|
||||||
}, [chatOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header className={`${s.header}`}>
|
<Header className={`${s.header}`}>
|
||||||
|
Logo goes here
|
||||||
{name}
|
{name}
|
||||||
<button onClick={() => setChatOpen(!chatOpen)}>Toggle Chat</button>
|
<UserDropdown />
|
||||||
</Header>
|
</Header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import Sider from 'antd/lib/layout/Sider';
|
import Sider from 'antd/lib/layout/Sider';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { chatCurrentlyVisible } from '../../stores/ClientConfigStore';
|
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||||
|
import ChatContainer from '../../chat/ChatContainer';
|
||||||
|
import { chatMessages } from '../../stores/ClientConfigStore';
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
let chatOpen = useRecoilValue(chatCurrentlyVisible);
|
const messages = useRecoilValue<ChatMessage[]>(chatMessages);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sider
|
<Sider
|
||||||
collapsed={!chatOpen}
|
collapsed={false}
|
||||||
width={300}
|
width={300}
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
@@ -14,6 +17,8 @@ export default function Sidebar() {
|
|||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<ChatContainer messages={messages} />
|
||||||
|
</Sider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
7
web/interfaces/follower.ts
Normal file
7
web/interfaces/follower.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Follower {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
username?: string;
|
||||||
|
image?: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
22
web/stories/Follower.stories.tsx
Normal file
22
web/stories/Follower.stories.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import * as FollowerComponent from '../components/Follower';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'owncast/Follower',
|
||||||
|
component: FollowerComponent,
|
||||||
|
parameters: {},
|
||||||
|
} as ComponentMeta<typeof FollowerComponent>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof FollowerComponent> = args => <FollowerComponent {...args} />;
|
||||||
|
|
||||||
|
export const Example = Template.bind({});
|
||||||
|
Example.args = {
|
||||||
|
follower: {
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
61
web/stories/Followercollection.stories.tsx
Normal file
61
web/stories/Followercollection.stories.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import FollowerCollection from '../components/FollowersCollection';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'owncast/Follower collection',
|
||||||
|
component: FollowerCollection,
|
||||||
|
parameters: {},
|
||||||
|
} as ComponentMeta<typeof FollowerCollection>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof FollowerCollection> = args => (
|
||||||
|
<FollowerCollection {...args} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Example = Template.bind({});
|
||||||
|
Example.args = {
|
||||||
|
followers: [
|
||||||
|
{
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John Doe',
|
||||||
|
description: 'User',
|
||||||
|
username: '@account@domain.tld',
|
||||||
|
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||||
|
link: 'https://yahoo.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
18
web/stories/Footer.stories.tsx
Normal file
18
web/stories/Footer.stories.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import Footer from '../components/ui/Footer/Footer';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'owncast/Footer',
|
||||||
|
component: Footer,
|
||||||
|
parameters: {},
|
||||||
|
} as ComponentMeta<typeof Footer>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-Footer
|
||||||
|
const Template: ComponentStory<typeof Footer> = args => <Footer {...args} />;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export const Example = Template.bind({});
|
||||||
|
Example.args = {
|
||||||
|
version: 'v1.2.3',
|
||||||
|
};
|
||||||
16
web/stories/Header.stories.tsx
Normal file
16
web/stories/Header.stories.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import Header from '../components/ui/Header/Header';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'owncast/Header',
|
||||||
|
component: Header,
|
||||||
|
parameters: {},
|
||||||
|
} as ComponentMeta<typeof Header>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof Header> = args => <Header {...args} />;
|
||||||
|
|
||||||
|
export const Example = Template.bind({});
|
||||||
|
Example.args = {
|
||||||
|
name: 'Example Stream Name',
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user