diff --git a/web/components/CustomPageContent.tsx b/web/components/CustomPageContent.tsx new file mode 100644 index 000000000..5a0bfbfcb --- /dev/null +++ b/web/components/CustomPageContent.tsx @@ -0,0 +1,8 @@ +interface Props { + content: string; +} + +export default function CustomPageContent(props: Props) { + const { content } = props; + return
{content}
; +} diff --git a/web/components/ExternalActionButton.tsx b/web/components/ExternalActionButton.tsx new file mode 100644 index 000000000..265585787 --- /dev/null +++ b/web/components/ExternalActionButton.tsx @@ -0,0 +1,9 @@ +import { ExternalAction } from '../interfaces/external-action.interface'; + +interface Props { + action: ExternalAction; +} + +export default function ExternalActionButton(props: Props) { + return
External action button component goes here
; +} diff --git a/web/components/PageLogo.tsx b/web/components/PageLogo.tsx new file mode 100644 index 000000000..9e9c4cabe --- /dev/null +++ b/web/components/PageLogo.tsx @@ -0,0 +1,7 @@ +interface Props { + url: string; +} + +export default function PageLogo(props: Props) { + return
Pimary logo component goes here
; +} diff --git a/web/components/SocialLinks.tsx b/web/components/SocialLinks.tsx new file mode 100644 index 000000000..7da70e602 --- /dev/null +++ b/web/components/SocialLinks.tsx @@ -0,0 +1,9 @@ +import { SocialLink } from '../interfaces/social-link.model'; + +interface Props { + links: SocialLink[]; +} + +export default function SocialLinks(props: Props) { + return
Social links component goes here
; +} diff --git a/web/components/UserDropdownMenu.tsx b/web/components/UserDropdownMenu.tsx new file mode 100644 index 000000000..36e5bc548 --- /dev/null +++ b/web/components/UserDropdownMenu.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function UserDropdown(props: Props) { + return
User settings dropdown component goes here
; +} diff --git a/web/components/chat/ChatActionMessage.jsx b/web/components/chat/ChatActionMessage.jsx new file mode 100644 index 000000000..69dd2f0e1 --- /dev/null +++ b/web/components/chat/ChatActionMessage.jsx @@ -0,0 +1,9 @@ +import { ChatMessage } from '../../interfaces/chat-message.model'; + +interface Props { + message: ChatMessage; +} + +export default function ChatSystemMessage(props: Props) { + return
Component goes here
; +} diff --git a/web/components/chat/ChatContainer.tsx b/web/components/chat/ChatContainer.tsx new file mode 100644 index 000000000..d793bee62 --- /dev/null +++ b/web/components/chat/ChatContainer.tsx @@ -0,0 +1,9 @@ +import { ChatMessage } from '../../interfaces/chat-message.model'; + +interface Props { + messages: ChatMessage[]; +} + +export default function ChatContainer(props: Props) { + return
Component goes here
; +} diff --git a/web/components/chat/ChatModeratorNotification.tsx b/web/components/chat/ChatModeratorNotification.tsx new file mode 100644 index 000000000..3d38f4248 --- /dev/null +++ b/web/components/chat/ChatModeratorNotification.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function ChatModerationNotification(props: Props) { + return
You are now a moderator notification component goes here
; +} diff --git a/web/components/chat/ChatSocialMessage.jsx b/web/components/chat/ChatSocialMessage.jsx new file mode 100644 index 000000000..53f4f2fde --- /dev/null +++ b/web/components/chat/ChatSocialMessage.jsx @@ -0,0 +1,9 @@ +import { ChatMessage } from '../../interfaces/chat-message.model'; + +interface Props { + message: ChatMessage; +} + +export default function ChatSocialMessage(props: Props) { + return
Component goes here
; +} diff --git a/web/components/chat/ChatSystemMessage.tsx b/web/components/chat/ChatSystemMessage.tsx new file mode 100644 index 000000000..69dd2f0e1 --- /dev/null +++ b/web/components/chat/ChatSystemMessage.tsx @@ -0,0 +1,9 @@ +import { ChatMessage } from '../../interfaces/chat-message.model'; + +interface Props { + message: ChatMessage; +} + +export default function ChatSystemMessage(props: Props) { + return
Component goes here
; +} diff --git a/web/components/chat/ChatTextField.tsx b/web/components/chat/ChatTextField.tsx new file mode 100644 index 000000000..00d908bc6 --- /dev/null +++ b/web/components/chat/ChatTextField.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function ChatTextField(props: Props) { + return
Component goes here
; +} diff --git a/web/components/chat/ChatUserMessage.tsx b/web/components/chat/ChatUserMessage.tsx new file mode 100644 index 000000000..e51053293 --- /dev/null +++ b/web/components/chat/ChatUserMessage.tsx @@ -0,0 +1,9 @@ +import { ChatMessage } from '../../interfaces/chat-message.model'; + +interface Props { + message: ChatMessage; +} + +export default function ChatUserMessage(props: Props) { + return
Component goes here
; +} diff --git a/web/components/layouts/main.tsx b/web/components/layouts/main.tsx index bb54481f3..5c0183fc3 100644 --- a/web/components/layouts/main.tsx +++ b/web/components/layouts/main.tsx @@ -1,10 +1,10 @@ import { useRecoilValue } from 'recoil'; import { Layout, Row, Col } from 'antd'; import { useState } from 'react'; -import { ServerStatus } from '../../models/ServerStatus'; +import { ServerStatus } from '../../interfaces/server-status.model'; import { ServerStatusStore, serverStatusState } from '../stores/ServerStatusStore'; import { ClientConfigStore, clientConfigState } from '../stores/ClientConfigStore'; -import { ClientConfig } from '../../models/ClientConfig'; +import { ClientConfig } from '../../interfaces/client-config.model'; const { Header, Content, Footer, Sider } = Layout; diff --git a/web/components/modals/AuthModal.tsx b/web/components/modals/AuthModal.tsx new file mode 100644 index 000000000..e51a44778 --- /dev/null +++ b/web/components/modals/AuthModal.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function AuthModal(props: Props) { + return
Component goes here
; +} diff --git a/web/components/modals/BrowserNotifyModal.tsx b/web/components/modals/BrowserNotifyModal.tsx new file mode 100644 index 000000000..cdfeff3d1 --- /dev/null +++ b/web/components/modals/BrowserNotifyModal.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function BrowserNotifyModal(props: Props) { + return
Component goes here
; +} diff --git a/web/components/modals/FediAuthModal.tsx b/web/components/modals/FediAuthModal.tsx new file mode 100644 index 000000000..a01d66282 --- /dev/null +++ b/web/components/modals/FediAuthModal.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function FediAuthModal(props: Props) { + return
Component goes here
; +} diff --git a/web/components/modals/FollowModal.tsx b/web/components/modals/FollowModal.tsx new file mode 100644 index 000000000..d769dce1a --- /dev/null +++ b/web/components/modals/FollowModal.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function FollowModal(props: Props) { + return
Component goes here
; +} diff --git a/web/components/modals/IndieAuthModal.tsx b/web/components/modals/IndieAuthModal.tsx new file mode 100644 index 000000000..dcd81d185 --- /dev/null +++ b/web/components/modals/IndieAuthModal.tsx @@ -0,0 +1,5 @@ +interface Props {} + +export default function IndieAuthModal(props: Props) { + return
Component goes here
; +} diff --git a/web/components/stores/ClientConfigStore.tsx b/web/components/stores/ClientConfigStore.tsx index 5de9ae8d3..d4b026edd 100644 --- a/web/components/stores/ClientConfigStore.tsx +++ b/web/components/stores/ClientConfigStore.tsx @@ -1,14 +1,25 @@ 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'; +import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model'; +import ClientConfigService from '../../services/client-config-service'; +// The config that comes from the API. export const clientConfigState = atom({ key: 'clientConfigState', default: makeEmptyClientConfig(), }); +export const chatCurrentlyVisible = atom({ + key: 'chatvisible', + default: false, +}); + +export const chatDislayName = atom({ + key: 'chatDisplayName', + default: '', +}); + export function ClientConfigStore(): ReactElement { const [, setClientConfig] = useRecoilState(clientConfigState); diff --git a/web/components/stores/ServerStatusStore.tsx b/web/components/stores/ServerStatusStore.tsx index e4b3df6d6..9db567423 100644 --- a/web/components/stores/ServerStatusStore.tsx +++ b/web/components/stores/ServerStatusStore.tsx @@ -1,8 +1,8 @@ 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'; +import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model'; +import ServerStatusService from '../../services/status-service'; export const serverStatusState = atom({ key: 'serverStatusState', diff --git a/web/components/video/owncastplayer.tsx b/web/components/video/OwncastPlayer.tsx similarity index 100% rename from web/components/video/owncastplayer.tsx rename to web/components/video/OwncastPlayer.tsx diff --git a/web/components/video/StatusBar.jsx b/web/components/video/StatusBar.jsx new file mode 100644 index 000000000..69304ff33 --- /dev/null +++ b/web/components/video/StatusBar.jsx @@ -0,0 +1,9 @@ +interface Props { + online: boolean; + viewers: number; + timer: string; +} + +export default function StatusBar(props: Props) { + return
Stream status bar goes here
; +} diff --git a/web/interfaces/chat-message.model.ts b/web/interfaces/chat-message.model.ts new file mode 100644 index 000000000..fda788647 --- /dev/null +++ b/web/interfaces/chat-message.model.ts @@ -0,0 +1 @@ +export interface ChatMessage {} diff --git a/web/models/ClientConfig.ts b/web/interfaces/client-config.model.ts similarity index 100% rename from web/models/ClientConfig.ts rename to web/interfaces/client-config.model.ts diff --git a/web/interfaces/external-action.tsx b/web/interfaces/external-action.tsx new file mode 100644 index 000000000..145b42ea7 --- /dev/null +++ b/web/interfaces/external-action.tsx @@ -0,0 +1,6 @@ +export interface ExternalAction { + title: string; + description?: string; + color?: string; + url: string; +} diff --git a/web/models/ServerStatus.ts b/web/interfaces/server-status.model.ts similarity index 100% rename from web/models/ServerStatus.ts rename to web/interfaces/server-status.model.ts diff --git a/web/interfaces/social-link.model.ts b/web/interfaces/social-link.model.ts new file mode 100644 index 000000000..200105cb4 --- /dev/null +++ b/web/interfaces/social-link.model.ts @@ -0,0 +1,5 @@ +export interface SocialLink { + platform: string; + icon: string; + url: string; +} diff --git a/web/services/ClientConfigService.ts b/web/services/client-config-service.ts similarity index 81% rename from web/services/ClientConfigService.ts rename to web/services/client-config-service.ts index 27f2d2753..b6b125a77 100644 --- a/web/services/ClientConfigService.ts +++ b/web/services/client-config-service.ts @@ -1,4 +1,4 @@ -import { ClientConfig } from '../models/ClientConfig'; +import { ClientConfig } from '../interfaces/client-config.model'; const ENDPOINT = `http://localhost:8080/api/config`; class ClientConfigService { diff --git a/web/services/StatusService.ts b/web/services/status-service.ts similarity index 82% rename from web/services/StatusService.ts rename to web/services/status-service.ts index 296332f3b..f9f1b9c25 100644 --- a/web/services/StatusService.ts +++ b/web/services/status-service.ts @@ -1,4 +1,4 @@ -import ServerStatus from '../models/ServerStatus'; +import ServerStatus from '../interfaces/server-status.model'; const ENDPOINT = `http://localhost:8080/api/status`; diff --git a/web/services/UserService.ts b/web/services/user-service.ts similarity index 99% rename from web/services/UserService.ts rename to web/services/user-service.ts index 66bc7bdc1..78c890599 100644 --- a/web/services/UserService.ts +++ b/web/services/user-service.ts @@ -15,7 +15,7 @@ class UserService { }, body: JSON.stringify({ displayName: username }), }; - + try { const response = await fetch(URL_CHAT_REGISTRATION, options); const result = await response.json(); @@ -26,4 +26,4 @@ class UserService { return null; } -} \ No newline at end of file +} diff --git a/web/stories/AuthModal.stories.tsx b/web/stories/AuthModal.stories.tsx new file mode 100644 index 000000000..2e4c9290e --- /dev/null +++ b/web/stories/AuthModal.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import AuthModal from '../components/modals/AuthModal'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Modals/Auth', + component: AuthModal, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/BrowserNotifyModal.stories.tsx b/web/stories/BrowserNotifyModal.stories.tsx new file mode 100644 index 000000000..2113053d8 --- /dev/null +++ b/web/stories/BrowserNotifyModal.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import BrowserNotifyModal from '../components/modals/BrowserNotifyModal'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Modals/Browser Push Notifications', + component: BrowserNotifyModal, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatActionMessage.stories.tsx b/web/stories/ChatActionMessage.stories.tsx new file mode 100644 index 000000000..769d9ec39 --- /dev/null +++ b/web/stories/ChatActionMessage.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ChatActionMessage from '../components/chat/ChatActionMessage'; + +export default { + title: 'owncast/Chat/Messages/Chat action', + component: ChatActionMessage, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatContainer.stories.tsx b/web/stories/ChatContainer.stories.tsx new file mode 100644 index 000000000..d08a96421 --- /dev/null +++ b/web/stories/ChatContainer.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ChatContainer from '../components/chat/ChatContainer'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Chat/Chat messages container', + component: ChatContainer, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatModerationNotification.stories.tsx b/web/stories/ChatModerationNotification.stories.tsx new file mode 100644 index 000000000..0848a5b69 --- /dev/null +++ b/web/stories/ChatModerationNotification.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ChatModeratorNotification from '../components/chat/ChatModeratorNotification'; + +export default { + title: 'owncast/Chat/Messages/Moderator notification', + component: ChatModeratorNotification, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ( + +); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatSocialMessage.stories.tsx b/web/stories/ChatSocialMessage.stories.tsx new file mode 100644 index 000000000..dd28227a6 --- /dev/null +++ b/web/stories/ChatSocialMessage.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ChatSocialMessage from '../components/chat/ChatSocialMessage'; + +export default { + title: 'owncast/Chat/Messages/Social-fediverse event', + component: ChatSocialMessage, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatSystemMessage.stories.tsx b/web/stories/ChatSystemMessage.stories.tsx new file mode 100644 index 000000000..d432981ae --- /dev/null +++ b/web/stories/ChatSystemMessage.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ChatSystemMessage from '../components/chat/ChatSystemMessage'; + +export default { + title: 'owncast/Chat/Messages/System', + component: ChatSystemMessage, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatTextField.stories.tsx b/web/stories/ChatTextField.stories.tsx new file mode 100644 index 000000000..ba1a4fd21 --- /dev/null +++ b/web/stories/ChatTextField.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ChatTextField from '../components/chat/ChatTextField'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Chat/Input text field', + component: ChatTextField, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/ChatUserMessage.stories.tsx b/web/stories/ChatUserMessage.stories.tsx new file mode 100644 index 000000000..02a7ee202 --- /dev/null +++ b/web/stories/ChatUserMessage.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import UserChatMessage from '../components/chat/ChatUserMessage'; + +export default { + title: 'owncast/Chat/Messages/Standard user', + component: UserChatMessage, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/CustomPageContent.stories.tsx b/web/stories/CustomPageContent.stories.tsx new file mode 100644 index 000000000..6cd9aaac4 --- /dev/null +++ b/web/stories/CustomPageContent.stories.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import CustomPageContent from '../components/CustomPageContent'; + +export default { + title: 'owncast/Custom page content', + component: CustomPageContent, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +export const Example1 = Template.bind({}); +Example1.args = { + content: `"\u003cp\u003eOwncast TV is a 24/7 live stream run by the Owncast project as an example of the software in use. Learn more about how you can have your own live stream that you completely control at \u003ca href=\"https://owncast.online\"\u003eowncast.online\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThis example instance shows how you can customize the page by changing things like fonts and colors as well as how you can add custom action buttons such as a donation button.\u003c/p\u003e\n\u003cp\u003eStay tuned in to learn about Owncast, hear from some streamers about their experiences using it, some bits and pieces of Owncast promo material, and highlights from other projects that are pretty cool.\u003c/p\u003e\n\u003cp\u003eBut when you've seen what we have to share with you, do yourself a favor and visit the \u003ca href=\"https://directory.owncast.online\"\u003eOwncast Directory\u003c/a\u003e and find an awesome stream to check out!\u003c/p\u003e\n\u003chr\u003e\n\u003ch2\u003eLinks to content seen in this stream\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://owncast.online/quickstart/\"\u003eOwncast Install Quickstart\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://directory.owncast.online\"\u003eOwncast Directory\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://creativecommons.org\"\u003eCreative Commons\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://tilvids.com\"\u003eTILVids\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://studio.blender.org/\"\u003eBlender Studio\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://archive.org/details/computerchronicles\"\u003eComputer Chronicles\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://joinmastodon.org\"\u003eMastodon\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e",`, +}; + +export const Example2 = Template.bind({}); +Example2.args = { + content: `"

WHAT IS HAPPENING HERE

\n

Game That Tune Radio is live with fantastic video game music streaming around the clock! We've got music from NES, SNES, Sega Genesis, Nintendo 64, Playstation, PC, and more coming all the time! If it's been featured on our podcast, it's gonna be on this stream! We only play three songs from each game on our podcast, and we decided that everyone needs more tunes!

\n

We'll be updating this livestream with new games as they're played on the show, including your requests! To get priority in requesting games for the show, check out https://www.patreon.com/GameThatTune

\n

Be sure to check out our live recordings of the Game That Tune podcast! We broadcast every Wednesday night at 9 PM EST on our YouTube channel as well as https://www.twitch.tv/GameThatTune and https://www.facebook.com/GameThatTune\nTune in and join in on the fun! Find the podcast in iTunes every Wednesday morning or head to https://www.gamethattune.com!

\n

Visit https://www.patreon.com/GameThatTune to help us keep up this live stream and upgrade our equipment for the live show! We've got exclusive mixtapes for our patrons, and lots more stuff planned for the future, so consider helping us out!

\n

HOW IT WORKS

\n

Featuring music from over 1000 games! Check out https://music.gamethattune.com/songs for the full list and make a request from your favorite game!

\n

Now that you've seen the list of games, make a request in the chat!

\n

!sr + anything = general search
\n!gr + game title = random song from matching game
\n!cr + composer name = random song from matching composer
\n!tr + anything = random result only searching song titles
\n!rr + anything = random result from all searchable fields
\n!game gtt = starts a round of our guessing game for bonus points!

\n

We have gifs!

\n

Wanna see your favorite gif on screen? type !summon followed by the gif name! Want your favorite gif to take over the video? Type !spawn followed by the gif name!

\n

Still have questions? Ask the chatbot! type !info to...wait for it...get more info!

\n

Thanks for listening!

"`, +}; + +export const Example3 = Template.bind({}); +Example3.args = { + content: `
\n

The upcoming streams

\n

Thursday, Apr. 28:

\n\n

Friday, Apr. 29:

\n\n

Saturday, Apr. 30:

\n
    \n
  • 19:00 CEST / 17:00 UTC / 1:00 pm EDT
    \nImmer Samstags:Syberia II“ (deutsch)
  • \n
  • 23:30 CEST / 21:30 UTC / 5:30 pm EDT
    \nNight Walk:Linux Game Jam 2022“ (english)
  • \n
\n

Sunday, May 1:

\n
    \n
  • 22:00 CEST / 20:00 UTC / 4:00 pm EDT
    \nSpacy Sunday:Mass Effect 2 LE“ (english)
  • \n
\n

Monday, May 2:

\n
    \n
  • 22:00 CEST / 20:00 UTC / 4:00 pm EDT
    \nMonday Tactics:BATTLETECH“ (english)
  • \n
\n

Tuesday, May 3:

\n
    \n
  • 22:00 CEST / 20:00 UTC / 4:00 pm EDT
    \nEpic Tuesday:Terraformers“ (english)
  • \n
\n

Wednesday, May 4:

\n
    \n
  • 22:00 CEST / 20:00 UTC / 4:00 pm EDT
    \nNarrative Wednesday:Zniw Adventure“ (english)
  • \n
\n


\nI plan a duration of about 4 to 5 hours per stream.

\n

The VODs of my streams will be available on YouTube channel and on my hatVODs PeerTube channel (the last 14 days). Some additional videos can also be found on my haTube PeerTube channel.

\n

If this channel is offline you might want to checkout other great Owncast streams at the Owncast Directory.

\n
\n

Pro-tips for new viewers

\n

Change chat name

\n

When you visit an Owncast channel for the first time then you've been given a random chat name. You see this name on the top right above the chat box. Click on that name in order to pick your own preferred chat name.

\n

Chat formatting

\n

The chat supports some basic Markdown, like *Italic*, **Bold** and \`Code blocks\` (from the Owncast docs)

\n

Player shortcuts

\n

The web video player has some keyboard shortcuts for you to use:

\n
    \n
  • Play/Pause - Spacebar
  • \n
  • Volume up - 0
  • \n
  • Volume down - 9
  • \n
  • Mute - m
  • \n
  • Toggle fullscreen - f
  • \n
  • Toggle chat - c
  • \n
\n

(from the Owncast docs)

\n

Watch Owncast via IPTV

\n

If you have a tv platform/set top box (apple, amazon, roku) that you can install IPTV apps on then you can add the url https://directory.owncast.online/api/iptv to watch this and other Owncast channels on the big screen.

\n

Watch the stream in a media player

\n

The stream is using HLS standard, so you can use your favorite media player to watch the stream using the url https://live.hatnix.net/hls/stream.m3u8

\n

Joining chat-only

\n

In case you want to join only the chat (because you're watching via IPTV?) you can do that using this url: https://live.hatnix.net/embed/chat/readwrite

\n
\n

About me and this channel

\n

My name is hatniX (or Frank), 48 years old. I'm a PC gamer for over 30 years now, playing games on Linux since 1998. And this is what you'll see here on this channel, me playing games on Linux. Preferable with a focus on storytelling and atmosphere, rather than "pro-gaming".

\n

Fediverse

\n

You can follow this server in the Fediverse: @hatnix@live.hatnix.net, in order to get announcements when the stream starts.

\n

Owncast

\n

This server is powered by Owncast. Check out the Owncast-Browser-Extension for Firefox or for Chrome browsers to see whether I'm live. Another great way to see when I (or other Owncast streamers) go online, is to follow the Owncast bots on Mastodon or Twitter.

\n

Chat bot

\n

I'm using an experimantal chat bot here, which I have written specifically for Owncast. Use !help in chat to get a list of the available commands. The source code of the bot is available at github.com/hatniX/hatbot (Public Domain).

\n

In case you wonder how I display the chat onscreen, incl. the fade effect, check out my CSS file.

\n

Music

\n

The music here, if not part of the game I'm playing, is provided by Jamando. I've got a license for livestream music for gaming.

\n
\n

How you can support me

\n

If you want to support me, visit my streams, either lurk or be active in chat. You can also help me getting more attention, by telling others about my channel, or just boosting my stream announcements on Mastodon or retweeting them on Twitter.

\n

Liberapay

\n

Liberapay is a non-profit subscription platform, which means that they don't take a share. If you like, you can support me there, either with a one-time or a regular donation. Thank you so much!

\n

Ko-fi

\n

In case you want to buy me a coffee then you can do so at ko-fi. I appreciate that a lot!

\n

Support is never required, but always much appreciated. Thank you very much! <3

\n
\n

My tech specs

\n

PC Hardware:

\n
    \n
  • CPU: AMD Ryzen 9 5900X
  • \n
  • RAM: 32 GB
  • \n
  • GPU: AMD Radeon RX 6900 XT (16 GB)
  • \n
  • Keyboard: Logitech K295
  • \n
  • Trackball: Kensington Expert Mouse Trackball
  • \n
  • Mouse 1: Logitech M220
  • \n
  • Mouse 2: Razer Naga Left Handed Edition
  • \n
  • Microphone: Blue Yeti
  • \n
  • Earbuds: Linklike Classic 2
  • \n
  • Headphone: beyerdynamic DT 990 Pro
  • \n
  • Webcam(s): Vitade 960A Pro
  • \n
\n

PC Software:

\n
    \n
  • OS: Manjaro Linux
  • \n
  • Kernel: 5.17.1-3-MANJARO x86_64
  • \n
  • Desktop: Xfce 4.16.0
  • \n
  • Mesa: 21.3.8
  • \n
  • Broadcaster software: OBS Studio 27.2.4
  • \n
\n

Stream Server: Hetzner Cloud „CPX11“

\n
    \n
  • 2 vCPU AMD
  • \n
  • 2 GB RAM
  • \n
  • 40 GB disc
  • \n
  • 20TB Traffic
  • \n
  • Owncast v0.0.11
    \n​
  • \n
`, +}; diff --git a/web/stories/ExternalActionButton.stories.tsx b/web/stories/ExternalActionButton.stories.tsx new file mode 100644 index 000000000..81ff4b09b --- /dev/null +++ b/web/stories/ExternalActionButton.stories.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ExternalActionButton from '../components/ExternalActionButton'; + +export default { + title: 'owncast/External action button', + component: ExternalActionButton, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ( + +); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Example1 = Template.bind({}); +Example1.args = { + action: { + url: 'https://owncast.online/docs', + title: 'Documentation', + description: 'Owncast Documentation', + icon: 'https://owncast.online/images/logo.svg', + color: '#5232c8', + openExternally: false, + }, +}; + +export const Example2 = Template.bind({}); +Example2.args = { + action: { + url: 'https://opencollective.com/embed/owncast/donate', + title: 'Support Owncast', + description: 'Contribute to Owncast', + icon: 'https://opencollective.com/static/images/opencollective-icon.svg', + color: '#2b4863', + openExternally: false, + }, +}; diff --git a/web/stories/FediAuthModal.stories.tsx b/web/stories/FediAuthModal.stories.tsx new file mode 100644 index 000000000..f342c390e --- /dev/null +++ b/web/stories/FediAuthModal.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import FediAuthModal from '../components/modals/FediAuthModal'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Modals/FediAuth', + component: FediAuthModal, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/FollowModal.stories.tsx b/web/stories/FollowModal.stories.tsx new file mode 100644 index 000000000..07dfa0c47 --- /dev/null +++ b/web/stories/FollowModal.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import FollowModal from '../components/modals/FollowModal'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Modals/Follow', + component: FollowModal, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/Form.stories.tsx b/web/stories/Form.stories.tsx index a7c7c995e..9c7655666 100644 --- a/web/stories/Form.stories.tsx +++ b/web/stories/Form.stories.tsx @@ -99,7 +99,7 @@ const FormExample = () => { }; export default { - title: 'owncast/Form', + title: 'example/Form', component: Form, // parameters: {}, } as ComponentMeta; diff --git a/web/stories/IndieAuthModal.stories.tsx b/web/stories/IndieAuthModal.stories.tsx new file mode 100644 index 000000000..660ea0c4b --- /dev/null +++ b/web/stories/IndieAuthModal.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import IndieAuthModal from '../components/modals/IndieAuthModal'; + +const Example = () => ( +
+ +
+); + +export default { + title: 'owncast/Modals/IndieAuth', + component: IndieAuthModal, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Basic = Template.bind({}); diff --git a/web/stories/Modal.stories.tsx b/web/stories/Modal.stories.tsx index 4e0d24229..de81a48c8 100644 --- a/web/stories/Modal.stories.tsx +++ b/web/stories/Modal.stories.tsx @@ -2,37 +2,16 @@ import React, { useState } from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { Modal, Button } from 'antd'; -const Usage = () => { - const [isModalVisible, setIsModalVisible] = useState(false); - - const showModal = () => { - setIsModalVisible(true); - }; - - const handleOk = () => { - setIsModalVisible(false); - }; - - const handleCancel = () => { - setIsModalVisible(false); - }; - - return ( - <> - - -

Some contents...

-

Some contents...

-

Some contents...

-
- - ); -}; +const Usage = () => ( + +

Some contents...

+

Some contents...

+

Some contents...

+
+); export default { - title: 'owncast/Modal', + title: 'owncast/Modal container', component: Modal, parameters: {}, } as ComponentMeta; @@ -40,8 +19,4 @@ export default { // eslint-disable-next-line @typescript-eslint/no-unused-vars const Template: ComponentStory = args => ; -export const Basic = Template.bind({}); - -Usage.propTypes = {}; - -Usage.defaultProps = {}; +export const Example = Template.bind({}); diff --git a/web/stories/PageLogo.stories.tsx b/web/stories/PageLogo.stories.tsx new file mode 100644 index 000000000..24c53a319 --- /dev/null +++ b/web/stories/PageLogo.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import PageLogo from '../components/PageLogo'; + +export default { + title: 'owncast/Page Logo', + component: PageLogo, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Logo = Template.bind({}); +Logo.args = { + url: '/logo', +}; + +export const DemoServer = Template.bind({}); +DemoServer.args = { + url: 'https://watch.owncast.online/logo', +}; diff --git a/web/stories/SocialLinks.stories.tsx b/web/stories/SocialLinks.stories.tsx new file mode 100644 index 000000000..f12e6fb37 --- /dev/null +++ b/web/stories/SocialLinks.stories.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import SocialLinks from '../components/SocialLinks'; + +export default { + title: 'owncast/Social links', + component: SocialLinks, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Populated = Template.bind({}); +Populated.args = { + links: [ + { + platform: 'github', + url: 'https://github.com/owncast/owncast', + icon: '/img/platformlogos/github.svg', + }, + { platform: 'Documentation', url: 'https://owncast.online' }, + { + platform: 'mastodon', + url: 'https://fosstodon.org/users/owncast', + icon: '/img/platformlogos/mastodon.svg', + }, + ], +}; + +export const Empty = Template.bind({}); +Empty.args = { + links: [], +}; diff --git a/web/stories/StatusBar.stories.tsx b/web/stories/StatusBar.stories.tsx new file mode 100644 index 000000000..a1469a89d --- /dev/null +++ b/web/stories/StatusBar.stories.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import StatusBar from '../components/video/StatusBar'; + +export default { + title: 'owncast/Status bar', + component: StatusBar, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Online = Template.bind({}); +Online.args = { + online: true, + viewers: 42, + timer: '10:42', +}; + +export const Offline = Template.bind({}); +Offline.args = { + online: false, +}; diff --git a/web/stories/UserDropdownMenu.stories.tsx b/web/stories/UserDropdownMenu.stories.tsx new file mode 100644 index 000000000..06fd4b5bc --- /dev/null +++ b/web/stories/UserDropdownMenu.stories.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import UserDropdownMenu from '../components/UserDropdownMenu'; + +export default { + title: 'owncast/User settings dropdown menu', + component: UserDropdownMenu, + parameters: {}, +} as ComponentMeta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Template: ComponentStory = args => ; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const Example = Template.bind({}); diff --git a/web/stories/Video.stories.tsx b/web/stories/Video.stories.tsx index c69edfdd2..ddc39a8a9 100644 --- a/web/stories/Video.stories.tsx +++ b/web/stories/Video.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentStory, ComponentMeta } from '@storybook/react'; -import OwncastPlayer from '../components/video/owncastplayer'; +import OwncastPlayer from '../components/video/OwncastPlayer'; const streams = { DemoServer: `https://watch.owncast.online`, diff --git a/web/utils/constants.js b/web/utils/constants.js index 31201aa0a..6e5d87865 100644 --- a/web/utils/constants.js +++ b/web/utils/constants.js @@ -12,9 +12,9 @@ export const URL_BAN_USER = `/api/chat/users/setenabled`; // TODO: This directory is customizable in the config. So we should expose this via the config API. export const URL_STREAM = `/hls/stream.m3u8`; -export const URL_WEBSOCKET = `${ - location.protocol === 'https:' ? 'wss' : 'ws' -}://${location.host}/ws`; +export const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${ + location.host +}/ws`; export const URL_CHAT_REGISTRATION = `/api/chat/register`; export const URL_FOLLOWERS = `/api/followers`; export const URL_PLAYBACK_METRICS = `/api/metrics/playback`; @@ -43,8 +43,7 @@ export const KEY_USERNAME = 'owncast_username'; export const KEY_CUSTOM_USERNAME_SET = 'owncast_custom_username_set'; export const KEY_CHAT_DISPLAYED = 'owncast_chat'; export const KEY_CHAT_FIRST_MESSAGE_SENT = 'owncast_first_message_sent'; -export const CHAT_INITIAL_PLACEHOLDER_TEXT = - 'Type here to chat, no account necessary.'; +export const CHAT_INITIAL_PLACEHOLDER_TEXT = 'Type here to chat, no account necessary.'; export const CHAT_PLACEHOLDER_TEXT = 'Message'; export const CHAT_PLACEHOLDER_OFFLINE = 'Chat is offline.'; export const CHAT_MAX_MESSAGE_LENGTH = 500; @@ -71,8 +70,7 @@ export const ORIENTATION_PORTRAIT = 'portrait'; export const ORIENTATION_LANDSCAPE = 'landscape'; // localstorage keys -export const HAS_DISPLAYED_NOTIFICATION_MODAL_KEY = - 'HAS_DISPLAYED_NOTIFICATION_MODAL'; +export const HAS_DISPLAYED_NOTIFICATION_MODAL_KEY = 'HAS_DISPLAYED_NOTIFICATION_MODAL'; export const USER_VISIT_COUNT_KEY = 'USER_VISIT_COUNT'; export const USER_DISMISSED_ANNOYING_NOTIFICATION_POPUP_KEY = 'USER_DISMISSED_ANNOYING_NOTIFICATION_POPUP_KEY'; diff --git a/web/utils/helpers.js b/web/utils/helpers.js index d12cdda2e..fc2919644 100644 --- a/web/utils/helpers.js +++ b/web/utils/helpers.js @@ -40,7 +40,7 @@ export function jumpToBottom(element, behavior) { }); }, 50, - element + element, ); }