Browse Source

Fix web project build errors

pull/2032/head
Gabe Kangas 3 years ago
parent
commit
72c01e1b9a
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
  1. 2
      .github/workflows/javascript-formatting.yml
  2. 1
      web/.eslintrc.js
  3. 2
      web/.prettierrc
  4. 24
      web/.vscode/settings.json
  5. 10
      web/README.md
  6. 1
      web/components/CustomPageContent.tsx
  7. 6
      web/components/PageLogo.tsx
  8. 2
      web/components/SocialLinks.tsx
  9. 2
      web/components/action-buttons/ActionButton.module.scss
  10. 2
      web/components/action-buttons/ActionButton.tsx
  11. 2
      web/components/chat/ChatActionMessage.tsx
  12. 2
      web/components/chat/ChatContainer.tsx
  13. 1
      web/components/chat/ChatModeratorNotification.tsx
  14. 2
      web/components/chat/ChatSocialMessage.tsx
  15. 2
      web/components/chat/ChatSystemMessage.tsx
  16. 1
      web/components/chat/ChatTextField.tsx
  17. 6
      web/components/chat/ChatTextField/ChatTextField.module.scss
  18. 53
      web/components/chat/ChatTextField/ChatTextField.tsx
  19. 36
      web/components/chat/ChatTextField/EmojiPicker.tsx
  20. 2
      web/components/chat/ChatUserMessage.tsx
  21. 43
      web/components/chat/chat.js
  22. 17
      web/components/common/Logo/Logo.tsx
  23. 9
      web/components/common/UserDropdown/UserDropdown.tsx
  24. 2
      web/components/common/UserDropdown/index.ts
  25. 4
      web/components/common/index.ts
  26. 2
      web/components/layouts/admin-layout.tsx
  27. 2
      web/components/main-layout.tsx
  28. 1
      web/components/modals/AuthModal.tsx
  29. 1
      web/components/modals/BrowserNotifyModal.tsx
  30. 1
      web/components/modals/FediAuthModal.tsx
  31. 1
      web/components/modals/FollowModal.tsx
  32. 1
      web/components/modals/IndieAuthModal.tsx
  33. 4
      web/components/offline-notice.tsx
  34. 7
      web/components/stores/ClientConfigStore.tsx
  35. 2
      web/components/stores/eventhandlers/connectedclientinfo.ts
  36. 40
      web/components/ui/Content/Content.module.scss
  37. 10
      web/components/ui/Content/Content.tsx
  38. 2
      web/components/ui/Content/index.ts
  39. 8
      web/components/ui/CrossfadeImage/CrossfadeImage.tsx
  40. 6
      web/components/ui/Footer/Footer.tsx
  41. 2
      web/components/ui/Header/Header.module.scss
  42. 5
      web/components/ui/Header/Header.tsx
  43. 2
      web/components/ui/Modal/Modal.module.scss
  44. 1
      web/components/ui/Modal/Modal.tsx
  45. 2
      web/components/ui/Sidebar/Sidebar.module.scss
  46. 2
      web/components/ui/Statusbar/Statusbar.module.scss
  47. 2
      web/components/ui/Statusbar/Statusbar.tsx
  48. 8
      web/components/ui/index.tsx
  49. 6
      web/components/video/OwncastPlayer.tsx
  50. 2
      web/components/video/Player.module.scss
  51. 2
      web/components/video/VideoPoster.module.scss
  52. 12
      web/components/video/player.tsx
  53. 50
      web/docs/README.md
  54. 1
      web/interfaces/client-config.model.ts
  55. 2
      web/interfaces/external-action.ts
  56. 689
      web/package-lock.json
  57. 4
      web/package.json
  58. 6
      web/pages/_app.tsx
  59. 2
      web/pages/admin/config-notify.tsx
  60. 31
      web/renovate.json
  61. 2
      web/services/status-service.ts
  62. 8
      web/services/websocket-service.ts
  63. 7
      web/stories/ActionButtonRow.stories.tsx
  64. 1
      web/stories/BrowserNotifyModal.stories.tsx
  65. 2
      web/stories/ChatActionMessage.stories.tsx
  66. 2
      web/stories/ChatSocialMessage.stories.tsx
  67. 3
      web/stories/ChatSystemMessage.stories.tsx
  68. 9
      web/stories/Color.tsx
  69. 3
      web/stories/Form.stories.tsx
  70. 25
      web/stories/ImageAsset.tsx
  71. 114
      web/stories/StyleGuide.stories.mdx
  72. 66
      web/stories/Tabs.stories.tsx
  73. 5
      web/style-definitions/README.md
  74. 32
      web/style-definitions/tokens/color/antd-overrides.yaml
  75. 34
      web/style-definitions/tokens/color/default-theme.yaml
  76. 92
      web/style-definitions/tokens/color/owncast-colors.yaml
  77. 1
      web/styles/ant-overrides.scss
  78. 12
      web/styles/config-storage.scss
  79. 20
      web/styles/form-misc-elements.scss
  80. 22
      web/styles/form-textfields.scss
  81. 3
      web/styles/globals.scss
  82. 5
      web/styles/pages.scss
  83. 11
      web/styles/theme.less
  84. 16
      web/tsconfig.json
  85. 10
      web/utils/hook-windowresize.tsx
  86. 14
      web/utils/urls.ts

2
.github/workflows/javascript-formatting.yml

@ -23,7 +23,7 @@ jobs: @@ -23,7 +23,7 @@ jobs:
uses: creyD/prettier_action@v4.2
with:
# This part is also where you can pass other options, for example:
prettier_options: --write webroot/**/*.{js,md}
prettier_options: --write web/**/*.{js,ts,jsx,tsx,css,md}
working_directory: web
only_changed: true
env:

1
web/.eslintrc.js

@ -40,6 +40,7 @@ module.exports = { @@ -40,6 +40,7 @@ module.exports = {
'@typescript-eslint/no-use-before-define': [1],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'no-restricted-exports': 'off',
'react/jsx-no-target-blank': [
1,
{

2
web/.prettierrc

@ -5,4 +5,4 @@ @@ -5,4 +5,4 @@
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "avoid"
}
}

24
web/.vscode/settings.json vendored

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
{
"cSpell.words": [
"Owncast",
"antd",
"bitrates",
"chartkick",
"framerates",
"kbps",
"linkify",
"paypal",
"toggleswitch"
]
}
"cSpell.words": [
"Owncast",
"antd",
"bitrates",
"chartkick",
"framerates",
"kbps",
"linkify",
"paypal",
"toggleswitch"
]
}

10
web/README.md

@ -8,21 +8,21 @@ The Owncast web frontend is a [Next.js](https://nextjs.org/) project with [React @@ -8,21 +8,21 @@ The Owncast web frontend is a [Next.js](https://nextjs.org/) project with [React
**First**, install the dependencies.
```npm install --include=dev```
`npm install --include=dev`
### Run the web project
Make sure you're running an instance of Owncast on localhost:8080, as your copy of the admin will look to use that as the API.
**Next**, start the web project with npm.
```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```
`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.
@ -63,4 +63,4 @@ We are currently experimenting with using [Storybook](https://storybook.js.org/) @@ -63,4 +63,4 @@ We are currently experimenting with using [Storybook](https://storybook.js.org/)
To work with Storybook:
```npm run storybook```
`npm run storybook`

1
web/components/CustomPageContent.tsx

@ -4,5 +4,6 @@ interface Props { @@ -4,5 +4,6 @@ interface Props {
export default function CustomPageContent(props: Props) {
const { content } = props;
// eslint-disable-next-line react/no-danger
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}

6
web/components/PageLogo.tsx

@ -1,7 +1,3 @@ @@ -1,7 +1,3 @@
interface Props {
url: string;
}
export default function PageLogo(props: Props) {
export default function PageLogo() {
return <div>Pimary logo component goes here</div>;
}

2
web/components/SocialLinks.tsx

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
import { SocialLink } from '../interfaces/social-link.model';
interface Props {
// eslint-disable-next-line react/no-unused-prop-types
links: SocialLink[];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function SocialLinks(props: Props) {
return <div>Social links component goes here</div>;
}

2
web/components/action-buttons/ActionButton.module.scss

@ -4,4 +4,4 @@ @@ -4,4 +4,4 @@
width: 20px;
margin-right: 3px;
}
}
}

2
web/components/action-buttons/ActionButton.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { Button } from 'antd';
import { useState } from 'react';
import Modal from '../ui/Modal/Modal';
import { ExternalAction } from '../interfaces/external-action.interface';
import { ExternalAction } from '../../interfaces/external-action';
import s from './ActionButton.module.scss';
interface Props {

2
web/components/chat/ChatActionMessage.jsx → web/components/chat/ChatActionMessage.tsx

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
import { ChatMessage } from '../../interfaces/chat-message.model';
interface Props {
// eslint-disable-next-line react/no-unused-prop-types
message: ChatMessage;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function ChatSystemMessage(props: Props) {
return <div>Component goes here</div>;
}

2
web/components/chat/ChatContainer.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Spin } from 'antd';
import { Virtuoso } from 'react-virtuoso';
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { useRef } from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { ChatMessage } from '../../interfaces/chat-message.model';
import { ChatState } from '../../interfaces/application-state';

1
web/components/chat/ChatModeratorNotification.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {}
export default function ChatModerationNotification(props: Props) {

2
web/components/chat/ChatSocialMessage.jsx → web/components/chat/ChatSocialMessage.tsx

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
/* eslint-disable react/no-unused-prop-types */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ChatMessage } from '../../interfaces/chat-message.model';
interface Props {

2
web/components/chat/ChatSystemMessage.tsx

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react/no-unused-prop-types */
import { ChatMessage } from '../../interfaces/chat-message.model';
interface Props {

1
web/components/chat/ChatTextField.tsx

@ -2,6 +2,7 @@ import { useState } from 'react'; @@ -2,6 +2,7 @@ import { useState } from 'react';
interface Props {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function ChatTextField(props: Props) {
const [value, setValue] = useState('');
const [showEmojis, setShowEmojis] = useState(false);

6
web/components/chat/ChatTextField/ChatTextField.module.scss

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
.root {
height: 2rem;
color: var(--black);
}
height: 2rem;
color: var(--black);
}

53
web/components/chat/ChatTextField/ChatTextField.tsx

@ -1,14 +1,13 @@ @@ -1,14 +1,13 @@
import { SmileOutlined } from '@ant-design/icons';
import { Button, Popover } from 'antd';
import React, { useState, useMemo, useRef, useEffect } from 'react';
import React, { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Transforms, createEditor, Node, BaseEditor, Text } from 'slate';
import { Transforms, createEditor, BaseEditor, Text } from 'slate';
import { Slate, Editable, withReact, ReactEditor } from 'slate-react';
import EmojiPicker from './EmojiPicker';
import WebsocketService from '../../../services/websocket-service';
import { websocketServiceAtom } from '../../stores/ClientConfigStore';
import { MessageType } from '../../../interfaces/socket-events';
import s from './ChatTextField.module.scss';
type CustomElement = { type: 'paragraph'; children: CustomText[] };
type CustomText = { text: string };
@ -25,24 +24,30 @@ interface Props { @@ -25,24 +24,30 @@ interface Props {
value?: string;
}
// eslint-disable-next-line react/prop-types
const Image = ({ element }) => (
<img
// eslint-disable-next-line no-undef
// eslint-disable-next-line react/prop-types
src={element.url}
alt="emoji"
style={{ display: 'inline', position: 'relative', width: '30px', bottom: '10px' }}
/>
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const insertImage = (editor, url) => {
const text = { text: '' };
const image: ImageElement = { type: 'image', url, children: [text] };
Transforms.insertNodes(editor, image);
// const text = { text: '' };
// const image: ImageElement = { type: 'image', url, children: [text] };
// Transforms.insertNodes(editor, image);
};
const withImages = editor => {
const { isVoid } = editor;
// eslint-disable-next-line no-param-reassign
editor.isVoid = element => (element.type === 'image' ? true : isVoid(element));
// eslint-disable-next-line no-param-reassign
editor.isInline = element => element.type === 'image';
return editor;
@ -52,13 +57,13 @@ export type EmptyText = { @@ -52,13 +57,13 @@ export type EmptyText = {
text: string;
};
type ImageElement = {
type: 'image';
url: string;
children: EmptyText[];
};
// type ImageElement = {
// type: 'image';
// url: string;
// children: EmptyText[];
// };
const Element = props => {
const Element = (props: any) => {
const { attributes, children, element } = props;
switch (element.type) {
@ -71,10 +76,10 @@ const Element = props => { @@ -71,10 +76,10 @@ const Element = props => {
const serialize = node => {
if (Text.isText(node)) {
let string = node.text;
if (node.bold) {
string = `<strong>${string}</strong>`;
}
const string = node.text;
// if (node.bold) {
// string = `<strong>${string}</strong>`;
// }
return string;
}
@ -90,8 +95,9 @@ const serialize = node => { @@ -90,8 +95,9 @@ const serialize = node => {
}
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function ChatTextField(props: Props) {
const { value: originalValue } = props;
// const { value: originalValue } = props;
const [showEmojis, setShowEmojis] = useState(false);
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const [editor] = useState(() => withImages(withReact(createEditor())));
@ -113,7 +119,7 @@ export default function ChatTextField(props: Props) { @@ -113,7 +119,7 @@ export default function ChatTextField(props: Props) {
Transforms.delete(editor);
};
const handleChange = e => {};
const handleChange = () => {};
const handleEmojiSelect = emoji => {
console.log(emoji);
@ -135,19 +141,12 @@ export default function ChatTextField(props: Props) { @@ -135,19 +141,12 @@ export default function ChatTextField(props: Props) {
}
};
const initialValue = [
{
type: 'paragraph',
children: [{ text: originalValue }],
},
];
return (
<div>
<Slate editor={editor} value={initialValue} onChange={handleChange}>
<Slate editor={editor} value={[]} onChange={handleChange}>
<Editable
onKeyDown={onKeyDown}
renderElement={props => <Element {...props} />}
renderElement={p => <Element {...p} />}
placeholder="Chat message goes here..."
/>
</Slate>

36
web/components/chat/ChatTextField/EmojiPicker.tsx

@ -1,22 +1,28 @@ @@ -1,22 +1,28 @@
import data from '@emoji-mart/data';
import React, { useRef, useEffect } from 'react';
// import data from '@emoji-mart/data';
import React, { useRef } from 'react';
export default function EmojiPicker(props) {
interface Props {
// eslint-disable-next-line react/no-unused-prop-types
onEmojiSelect: (emoji: string) => void;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function EmojiPicker(props: Props) {
const ref = useRef();
// TODO: Pull this custom emoji data in from the emoji API.
const custom = [
{
emojis: [
{
id: 'party_parrot',
name: 'Party Parrot',
keywords: ['dance', 'dancing'],
skins: [{ src: 'https://watch.owncast.online/img/emoji/bluntparrot.gif' }],
},
],
},
];
// const custom = [
// {
// emojis: [
// {
// id: 'party_parrot',
// name: 'Party Parrot',
// keywords: ['dance', 'dancing'],
// skins: [{ src: 'https://watch.owncast.online/img/emoji/bluntparrot.gif' }],
// },
// ],
// },
// ];
// TODO: Fix the emoji picker from throwing errors.
// useEffect(() => {

2
web/components/chat/ChatUserMessage.tsx

@ -7,7 +7,9 @@ interface Props { @@ -7,7 +7,9 @@ interface Props {
export default function ChatUserMessage(props: Props) {
const { message, showModeratorMenu } = props;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { body, user, timestamp } = message;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { displayName, displayColor } = user;
// TODO: Convert displayColor (a hue) to a usable color.

43
web/components/chat/chat.js

@ -101,45 +101,6 @@ export function convertToText(str = '') { @@ -101,45 +101,6 @@ export function convertToText(str = '') {
return value;
}
/*
You would call this when a user pastes from
the clipboard into a `contenteditable` area.
*/
export function convertOnPaste(event = { preventDefault() {} }, emojiList) {
// Prevent paste.
event.preventDefault();
// Set later.
let value = '';
// Does method exist?
const hasEventClipboard = !!(
event.clipboardData &&
typeof event.clipboardData === 'object' &&
typeof event.clipboardData.getData === 'function'
);
// Get clipboard data?
if (hasEventClipboard) {
value = event.clipboardData.getData('text/plain');
}
// Insert into temp `<textarea>`, read back out.
const textarea = document.createElement('textarea');
textarea.innerHTML = value;
value = textarea.innerText;
// Clean up text.
value = convertToText(value);
const HTML = emojify(value, emojiList);
// Insert text.
if (typeof document.execCommand === 'function') {
document.execCommand('insertHTML', false, HTML);
}
}
export function createEmojiMarkup(data, isCustom) {
const emojiUrl = isCustom ? data.emoji : data.url;
const emojiName = (
@ -156,6 +117,7 @@ export function trimNbsp(html) { @@ -156,6 +117,7 @@ export function trimNbsp(html) {
export function emojify(HTML, emojiList) {
const textValue = convertToText(HTML);
// eslint-disable-next-line no-plusplus
for (let lastPos = textValue.length; lastPos >= 0; lastPos--) {
const endPos = textValue.lastIndexOf(':', lastPos);
if (endPos <= 0) {
@ -170,8 +132,9 @@ export function emojify(HTML, emojiList) { @@ -170,8 +132,9 @@ export function emojify(HTML, emojiList) {
emojiItem => emojiItem.name.toLowerCase() === typedEmoji.toLowerCase(),
);
if (emojiIndex != -1) {
if (emojiIndex !== -1) {
const emojiImgElement = createEmojiMarkup(emojiList[emojiIndex], true);
// eslint-disable-next-line no-param-reassign
HTML = HTML.replace(`:${typedEmoji}:`, emojiImgElement);
}
}

17
web/components/common/Logo/Logo.tsx

@ -1,19 +1,16 @@ @@ -1,19 +1,16 @@
import React from 'react';
import cn from 'classnames';
import s from './Logo.module.scss'
import s from './Logo.module.scss';
interface Props {
variant: 'simple' | 'contrast'
variant: 'simple' | 'contrast';
}
export default function Logo({variant = 'simple'}: Props) {
const rootClassName = cn(
s.root,
{
[s.simple]: variant === 'simple',
[s.contrast]: variant === 'contrast',
}
)
export default function Logo({ variant = 'simple' }: Props) {
const rootClassName = cn(s.root, {
[s.simple]: variant === 'simple',
[s.contrast]: variant === 'contrast',
});
return (
<div className={rootClassName}>

9
web/components/common/UserDropdown/UserDropdown.tsx

@ -6,8 +6,8 @@ import { ChatState, ChatVisibilityState } from '../../../interfaces/application- @@ -6,8 +6,8 @@ import { ChatState, ChatVisibilityState } from '../../../interfaces/application-
import s from './UserDropdown.module.scss';
interface Props {
username?: string;
chatState?: ChatState;
username: string;
chatState: ChatState;
}
export default function UserDropdown({ username = 'test-user', chatState }: Props) {
@ -44,11 +44,6 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop @@ -44,11 +44,6 @@ export default function UserDropdown({ username = 'test-user', chatState }: Prop
<DownOutlined />
</Space>
</Button>
{/*
<button type="button" className="ant-dropdown-link" onClick={e => e.preventDefault()}>
{username} <DownOutlined />
</button>
*/}
</Dropdown>
</div>
);

2
web/components/common/UserDropdown/index.ts

@ -1 +1 @@ @@ -1 +1 @@
export { default } from './UserDropdown'
export { default } from './UserDropdown';

4
web/components/common/index.ts

@ -1,2 +1,2 @@ @@ -1,2 +1,2 @@
export { default as UserDropdown } from './UserDropdown'
export { default as OwncastLogo } from './Logo'
export { default as UserDropdown } from './UserDropdown';
export { default as OwncastLogo } from './Logo';

2
web/pages/admin/admin-layout.tsx → web/components/layouts/admin-layout.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { AppProps } from 'next/app';
import ServerStatusProvider from '../../utils/server-status-context';
import AlertMessageProvider from '../../utils/alert-message-context';
import MainLayout from '../../components/main-layout';
import MainLayout from '../main-layout';
function AdminLayout({ Component, pageProps }: AppProps) {
return (

2
web/components/main-layout.tsx

@ -130,7 +130,7 @@ export default function MainLayout(props) { @@ -130,7 +130,7 @@ export default function MainLayout(props) {
<Sider width={240} className="side-nav">
<h1 className="owncast-title">
<span className="logo-container">
<OwncastLogo />
<OwncastLogo variant="simple" />
</span>
<span className="title-label">Owncast Admin</span>
</h1>

1
web/components/modals/AuthModal.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {}
export default function AuthModal(props: Props) {

1
web/components/modals/BrowserNotifyModal.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {}
export default function BrowserNotifyModal(props: Props) {

1
web/components/modals/FediAuthModal.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {}
export default function FediAuthModal(props: Props) {

1
web/components/modals/FollowModal.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {}
export default function FollowModal(props: Props) {

1
web/components/modals/IndieAuthModal.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
interface Props {}
export default function IndieAuthModal(props: Props) {

4
web/components/offline-notice.tsx

@ -3,7 +3,7 @@ import { Card, Col, Row, Typography } from 'antd'; @@ -3,7 +3,7 @@ import { Card, Col, Row, Typography } from 'antd';
import Link from 'next/link';
import { useContext } from 'react';
import LogTable from './log-table';
import OwncastLogo from './logo';
import OwncastLogo from './common/Logo/Logo';
import NewsFeed from './news-feed';
import { ConfigDetails } from '../types/config-section';
import { ServerStatusContext } from '../utils/server-status-context';
@ -125,7 +125,7 @@ export default function Offline({ logs = [], config }: OfflineProps) { @@ -125,7 +125,7 @@ export default function Offline({ logs = [], config }: OfflineProps) {
<Col span={12} offset={6}>
<div className="offline-intro">
<span className="logo">
<OwncastLogo />
<OwncastLogo variant="simple" />
</span>
<div>
<Title level={2}>No stream is active</Title>

7
web/components/stores/ClientConfigStore.tsx

@ -6,7 +6,6 @@ import ClientConfigService from '../../services/client-config-service'; @@ -6,7 +6,6 @@ import ClientConfigService from '../../services/client-config-service';
import ChatService from '../../services/chat-service';
import WebsocketService from '../../services/websocket-service';
import { ChatMessage } from '../../interfaces/chat-message.model';
import { getLocalStorage, setLocalStorage } from '../../utils/helpers';
import {
AppState,
ChatState,
@ -16,10 +15,10 @@ import { @@ -16,10 +15,10 @@ import {
getChatVisibilityState,
} from '../../interfaces/application-state';
import {
SocketEvent,
ConnectedClientInfoEvent,
MessageType,
ChatEvent,
SocketEvent,
} from '../../interfaces/socket-events';
import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo';
import handleChatMessage from './eventhandlers/handleChatMessage';
@ -77,10 +76,8 @@ export function ClientConfigStore() { @@ -77,10 +76,8 @@ export function ClientConfigStore() {
const [chatMessages, setChatMessages] = useRecoilState<ChatMessage[]>(chatMessagesAtom);
const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom);
const [appState, setAppState] = useRecoilState<AppState>(appStateAtom);
const [videoState, setVideoState] = useRecoilState<VideoState>(videoStateAtom);
const [accessToken, setAccessToken] = useRecoilState<string>(accessTokenAtom);
const [websocketService, setWebsocketService] =
useRecoilState<WebsocketService>(websocketServiceAtom);
const setWebsocketService = useSetRecoilState<WebsocketService>(websocketServiceAtom);
let ws: WebsocketService;

2
web/components/stores/eventhandlers/connectedclientinfo.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { ConnectedClientInfoEvent, SocketEvent } from '../../../interfaces/socket-events';
import { ConnectedClientInfoEvent } from '../../../interfaces/socket-events';
export default function handleConnectedClientInfoMessage(message: ConnectedClientInfoEvent) {
console.log('connected client', message);

40
web/components/ui/Content/Content.module.scss

@ -1,26 +1,26 @@ @@ -1,26 +1,26 @@
.root {
display: grid;
grid-template-columns: 1fr;
display: grid;
grid-template-columns: 1fr;
}
.mobileChat {
display: block;
position: absolute;
background-color: white;
top: 0px;
width: 100%;
height: calc(50vh - var(--header-h));
display: block;
position: absolute;
background-color: white;
top: 0px;
width: 100%;
height: calc(50vh - var(--header-h));
}
.leftCol {
display: grid;
// -64px, which is the header
grid-template-rows: 50vh calc(50vh - var(--header-h));
display: grid;
// -64px, which is the header
grid-template-rows: 50vh calc(50vh - var(--header-h));
}
.lowerRow {
position: relative;
display: grid;
grid-template-rows: 1fr var(--header-h);
position: relative;
display: grid;
grid-template-rows: 1fr var(--header-h);
}
.pageContentSection {
@ -32,10 +32,10 @@ @@ -32,10 +32,10 @@
}
@media (min-width: 768px) {
.mobileChat {
display: none;
}
.root[data-columns='2'] {
grid-template-columns: 1fr var(--chat-w);
}
.mobileChat {
display: none;
}
.root[data-columns='2'] {
grid-template-columns: 1fr var(--chat-w);
}
}

10
web/components/ui/Content/Content.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import { useRecoilValue } from 'recoil';
import { Layout, Button, Col, Tabs } from 'antd';
import Grid from 'antd/lib/card/Grid';
import { Layout, Button, Tabs } from 'antd';
import {
chatVisibilityAtom,
clientConfigStateAtom,
@ -23,6 +22,7 @@ import ActionButtonRow from '../../action-buttons/ActionButtonRow'; @@ -23,6 +22,7 @@ import ActionButtonRow from '../../action-buttons/ActionButtonRow';
import ActionButton from '../../action-buttons/ActionButton';
import Statusbar from '../Statusbar/Statusbar';
import { ServerStatus } from '../../../interfaces/server-status.model';
import { Follower } from '../../../interfaces/follower';
const { TabPane } = Tabs;
const { Content } = Layout;
@ -34,8 +34,8 @@ export default function ContentComponent() { @@ -34,8 +34,8 @@ export default function ContentComponent() {
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
const chatState = useRecoilValue<ChatState>(chatStateAtom);
const { extraPageContent } = clientConfig;
const { online, viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } = status;
const { extraPageContent, version } = clientConfig;
const { online, viewerCount, lastConnectTime, lastDisconnectTime } = status;
const followers: Follower[] = [];
@ -88,7 +88,7 @@ export default function ContentComponent() { @@ -88,7 +88,7 @@ export default function ContentComponent() {
<ChatTextField />
</div>
)}
<Footer />
<Footer version={version} />
</div>
</div>
{chatOpen && <Sidebar />}

2
web/components/ui/Content/index.ts

@ -1 +1 @@ @@ -1 +1 @@
export { default } from "./Content"
export { default } from './Content';

8
web/components/ui/CrossfadeImage/CrossfadeImage.tsx

@ -54,11 +54,11 @@ export default function CrossfadeImage({ @@ -54,11 +54,11 @@ export default function CrossfadeImage({
return (
<span style={spanStyle}>
{[...srcs, nextSrc].map(
(src, index) =>
src !== '' && (
(singleSrc, index) =>
singleSrc !== '' && (
<img
key={(key + index) % 3}
src={src}
key={singleSrc}
src={singleSrc}
alt=""
style={imgStyles[index]}
onLoad={index === 2 ? onLoadImg : undefined}

6
web/components/ui/Footer/Footer.tsx

@ -2,7 +2,11 @@ import { Layout } from 'antd'; @@ -2,7 +2,11 @@ import { Layout } from 'antd';
const { Footer } = Layout;
export default function FooterComponent(props) {
interface Props {
version: string;
}
export default function FooterComponent(props: Props) {
const { version } = props;
return <Footer style={{ textAlign: 'center', height: '64px' }}>Footer: Owncast {version}</Footer>;

2
web/components/ui/Header/Header.module.scss

@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
align-items: center;
justify-content: space-between;
z-index: 1;
padding: .5rem 1rem;
padding: 0.5rem 1rem;
.logo {
display: flex;
align-items: center;

5
web/components/ui/Header/Header.tsx

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { Layout } from 'antd';
import { ChatState } from '../../../interfaces/application-state';
import { OwncastLogo, UserDropdown } from '../../common';
import s from './Header.module.scss';
@ -12,10 +13,10 @@ export default function HeaderComponent({ name = 'Your stream title' }: Props) { @@ -12,10 +13,10 @@ export default function HeaderComponent({ name = 'Your stream title' }: Props) {
return (
<Header className={`${s.header}`}>
<div className={`${s.logo}`}>
<OwncastLogo variant='contrast'/>
<OwncastLogo variant="contrast" />
<span>{name}</span>
</div>
<UserDropdown />
<UserDropdown username="fillmein" chatState={ChatState.Available} />
</Header>
);
}

2
web/components/ui/Modal/Modal.module.scss

@ -8,4 +8,4 @@ @@ -8,4 +8,4 @@
display: block;
height: 100%;
padding: 2vw;
}
}

1
web/components/ui/Modal/Modal.tsx

@ -29,7 +29,6 @@ export default function Modal(props: Props) { @@ -29,7 +29,6 @@ export default function Modal(props: Props) {
width="100%"
height="100%"
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
allowpaymentrequest="true"
frameBorder="0"
allowFullScreen
onLoad={() => setLoading(false)}

2
web/components/ui/Sidebar/Sidebar.module.scss

@ -8,5 +8,3 @@ @@ -8,5 +8,3 @@
display: flex;
}
}

2
web/components/ui/Statusbar/Statusbar.module.scss

@ -7,4 +7,4 @@ @@ -7,4 +7,4 @@
height: 30px;
width: 100%;
background-color: black;
}
}

2
web/components/ui/Statusbar/Statusbar.tsx

@ -40,7 +40,7 @@ export default function Statusbar(props: Props) { @@ -40,7 +40,7 @@ export default function Statusbar(props: Props) {
const duration = makeDurationString(new Date(lastConnectTime));
onlineMessage = online ? `Live for ${duration}` : 'Offline';
rightSideMessage = `${viewerCount > 0 ? `${viewerCount}` : 'No'} ${
viewerCount == 1 ? 'viewer' : 'viewers'
viewerCount === 1 ? 'viewer' : 'viewers'
}`;
} else {
onlineMessage = 'Offline';

8
web/components/ui/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
export { default as Header } from './Header/index'
export { default as Sidebar } from './Sidebar/index'
export { default as Footer } from './Footer/index'
export { default as Content } from './Content/index'
export { default as Header } from './Header/index';
export { default as Sidebar } from './Sidebar/index';
export { default as Footer } from './Footer/index';
export { default as Content } from './Content/index';

6
web/components/video/OwncastPlayer.tsx

@ -107,11 +107,7 @@ export default function OwncastPlayer(props: Props) { @@ -107,11 +107,7 @@ export default function OwncastPlayer(props: Props) {
<div style={{ display: 'grid' }}>
{online && (
<div style={{ gridColumn: 1, gridRow: 1 }}>
<VideoJS
style={{ gridColumn: 1, gridRow: 1 }}
options={videoJsOptions}
onReady={handlePlayerReady}
/>
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
</div>
)}
<div style={{ gridColumn: 1, gridRow: 1 }}>

2
web/components/video/Player.module.scss

@ -6,4 +6,4 @@ @@ -6,4 +6,4 @@
.vjs-big-play-centered .vjs-big-play-button {
z-index: 99999 !important;
}
}
}

2
web/components/video/VideoPoster.module.scss

@ -2,4 +2,4 @@ @@ -2,4 +2,4 @@
background-color: black;
display: flex;
justify-content: center;
}
}

12
web/components/video/player.tsx

@ -7,8 +7,12 @@ require('video.js/dist/video-js.css'); @@ -7,8 +7,12 @@ require('video.js/dist/video-js.css');
// TODO: Restore volume that was saved in local storage.
// import { getLocalStorage, setLocalStorage } from '../../utils/helpers.js';
// import { PLAYER_VOLUME, URL_STREAM } from '../../utils/constants.js';
interface Props {
options: any;
onReady: (player: videojs.Player) => void;
}
export function VideoJS(props) {
export function VideoJS(props: Props) {
const videoRef = React.useRef(null);
const playerRef = React.useRef(null);
const { options, onReady } = props;
@ -18,11 +22,10 @@ export function VideoJS(props) { @@ -18,11 +22,10 @@ export function VideoJS(props) {
if (!playerRef.current) {
const videoElement = videoRef.current;
// if (!videoElement) return;
// eslint-disable-next-line no-multi-assign
const player = (playerRef.current = videojs(videoElement, options, () => {
player.log('player is ready');
onReady && onReady(player);
return onReady && onReady(player);
}));
// TODO: Add airplay support, video settings menu, latency compensator, etc.
@ -48,6 +51,7 @@ export function VideoJS(props) { @@ -48,6 +51,7 @@ export function VideoJS(props) {
return (
<div data-vjs-player>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video ref={videoRef} className={`video-js vjs-big-play-centered ${s.player}`} />
</div>
);

50
web/docs/README.md

@ -1,19 +1,19 @@ @@ -1,19 +1,19 @@
# Tips for creating a new Admin form
### Layout
- Give your page or form a title. Feel free to use Ant Design's `<Title>` component.
- Give your form a description inside of a `<p className="description" />` tag.
- Use some Ant Design `Row` and `Col`'s to layout your forms if you want to spread them out into responsive columns. If you use an `<Row>`s, be sure to use `<Col>`s with them too!
- Use some Ant Design `Row` and `Col`'s to layout your forms if you want to spread them out into responsive columns. If you use an `<Row>`s, be sure to use `<Col>`s with them too!
- Use the `form-module` CSS class if you want to add a visual separation to a grouping of items.
### Form fields
- Feel free to use the pre-styled `<TextField>` text form field or the `<ToggleSwitch>` compnent, in a group of form fields together. These have been styled and laid out to match each other.
- `Slider`'s - If your form uses an Ant Slider component, follow this recommended markup of CSS classes to maintain a consistent look and feel to other Sliders in the app.
- `Slider`'s - If your form uses an Ant Slider component, follow this recommended markup of CSS classes to maintain a consistent look and feel to other Sliders in the app.
```
<div className="segment-slider-container">
<Slider ...props />
@ -22,24 +22,28 @@ @@ -22,24 +22,28 @@
```
### Submit Statuses
- It would be nice to display indicators of success/warnings to let users know if something has been successfully updated on the server. It has a lot of steps (sorry, but it could probably be optimized), but it'll provide a consistent way to display messaging.
- It would be nice to display indicators of success/warnings to let users know if something has been successfully updated on the server. It has a lot of steps (sorry, but it could probably be optimized), but it'll provide a consistent way to display messaging.
- See `reset-yp.tsx` for an example of using `submitStatus` with `useState()` and the `<FormStatusIndicator>` component to achieve this.
### Styling
- This admin site chooses to have a generally Dark color palette, but with colors that are different from Ant design's _dark_ stylesheet, so that style sheet is not included. This results in a very large `ant-overrides.scss` file to reset colors on frequently used Ant components in the system. If you find yourself a new Ant Component that has not yet been used in this app, feel free to add a reset style for that component to the overrides stylesheet.
- Take a look at `variables.scss` CSS file if you want to give some elements custom css colors.
- This admin site chooses to have a generally Dark color palette, but with colors that are different from Ant design's _dark_ stylesheet, so that style sheet is not included. This results in a very large `ant-overrides.scss` file to reset colors on frequently used Ant components in the system. If you find yourself a new Ant Component that has not yet been used in this app, feel free to add a reset style for that component to the overrides stylesheet.
- Take a look at `variables.scss` CSS file if you want to give some elements custom css colors.
---
---
# Creating Admin forms the Config section
# Creating Admin forms the Config section
First things first..
## General Config data flow in this React app
- When the Admin app loads, the `ServerStatusContext` (in addition to checking server `/status` on a timer) makes a call to the `/serverconfig` API to get your config details. This data will be stored as **`serverConfig`** in app state, and _provided_ to the app via `useContext` hook.
- When the Admin app loads, the `ServerStatusContext` (in addition to checking server `/status` on a timer) makes a call to the `/serverconfig` API to get your config details. This data will be stored as **`serverConfig`** in app state, and _provided_ to the app via `useContext` hook.
- The `serverConfig` in state is be the central source of data that pre-populates the forms.
@ -47,39 +51,42 @@ First things first.. @@ -47,39 +51,42 @@ First things first..
- After you have updated a config value in a form field, and successfully submitted it through its endpoint, you should call `setFieldInConfigState` to update the global state with the new value.
## Suggested Config Form Flow
- *NOTE: Each top field of the serverConfig has its own API update endpoint.*
- _NOTE: Each top field of the serverConfig has its own API update endpoint._
There many steps here, but they are highly suggested to ensure that Config values are updated and displayed properly throughout the entire admin form.
For each form input (or group of inputs) you make, you should:
1. Get the field values that you want out of `serverConfig` from ServerStatusContext with `useContext`.
2. Next we'll have to put these field values of interest into a `useState` in each grouping. This will help you edit the form.
3. Because ths config data is populated asynchronously, Use a `useEffect` to check when that data has arrived before putting it into state.
4. You will be using the state's value to populate the `defaultValue` and the `value` props of each Ant input component (`Input`, `Toggle`, `Switch`, `Select`, `Slider` are currently used).
5. When an `onChange` event fires for each type of input component, you will update the local state of each page with the changed value.
6. Depending on the form, an `onChange` of the input component, or a subsequent `onClick` of a submit button will take the value from local state and POST the field's API.
7. `onSuccess` of the post, you should update the global app state with the new value.
1. Get the field values that you want out of `serverConfig` from ServerStatusContext with `useContext`.
2. Next we'll have to put these field values of interest into a `useState` in each grouping. This will help you edit the form.
3. Because ths config data is populated asynchronously, Use a `useEffect` to check when that data has arrived before putting it into state.
4. You will be using the state's value to populate the `defaultValue` and the `value` props of each Ant input component (`Input`, `Toggle`, `Switch`, `Select`, `Slider` are currently used).
5. When an `onChange` event fires for each type of input component, you will update the local state of each page with the changed value.
6. Depending on the form, an `onChange` of the input component, or a subsequent `onClick` of a submit button will take the value from local state and POST the field's API.
7. `onSuccess` of the post, you should update the global app state with the new value.
There are also a variety of other local states to manage the display of error/success messaging.
- It is recommended that you use `form-textfield-with-submit` and `form-toggleswitch`(with `useSubmit=true`) Components to edit Config fields.
Examples of Config form groups where individual form fields submitting to the update API include:
- `edit-instance-details.tsx`
- `edit-server-details.tsx`
Examples of Config form groups where there is 1 submit button for the entire group include:
- `edit-storage.tsx`
Examples of Config form groups where there is 1 submit button for the entire group include:
- `edit-storage.tsx`
---
#### Notes about `form-textfield-with-submit` and `form-togglefield` (with useSubmit=true)
- The text field is intentionally designed to make it difficult for the user to submit bad data.
- If you make a change on a field, a Submit buttton will show up that you have to click to update. That will be the only way you can update it.
- If you clear out a field that is marked as Required, then exit/blur the field, it will repopulate with its original value.
- If you clear out a field that is marked as Required, then exit/blur the field, it will repopulate with its original value.
- Both of these elements are specifically meant to be used with updating `serverConfig` fields, since each field requires its own endpoint.
@ -88,4 +95,3 @@ Examples of Config form groups where there is 1 submit button for the entire gro @@ -88,4 +95,3 @@ Examples of Config form groups where there is 1 submit button for the entire gro
- (currently undergoing re-styling and TS cleanup)
- NOTE: you don't have to use these components. Some form groups may require a customized UX flow where you're better off using the Ant components straight up.

1
web/interfaces/client-config.model.ts

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
export interface ClientConfig {
name: string;
title?: string;
summary: string;
logo: string;
tags: string[];

2
web/interfaces/external-action.ts

@ -3,4 +3,6 @@ export interface ExternalAction { @@ -3,4 +3,6 @@ export interface ExternalAction {
description?: string;
color?: string;
url: string;
icon?: string;
openExternally?: boolean;
}

689
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

4
web/package.json

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
"@ant-design/icons": "4.7.0",
"@emoji-mart/data": "^1.0.1",
"@storybook/react": "^6.4.22",
"antd": "4.18.9",
"antd": "^4.20.4",
"autoprefixer": "^10.4.4",
"chart.js": "3.7.0",
"chartkick": "4.1.1",
@ -85,7 +85,7 @@ @@ -85,7 +85,7 @@
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"prettier": "2.5.1",
"prettier": "2.6.2",
"sass": "^1.50.0",
"sass-loader": "^10.1.1",
"sb": "^6.4.22",

6
web/pages/_app.tsx

@ -21,13 +21,13 @@ import '../styles/pages.scss'; @@ -21,13 +21,13 @@ import '../styles/pages.scss';
import '../styles/offline-notice.scss';
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { Router, useRouter } from 'next/router';
import AdminLayout from './admin/admin-layout';
import AdminLayout from '../components/layouts/admin-layout';
import SimpleLayout from '../components/layouts/SimpleLayout';
function App({ Component, pageProps }: AppProps) {
const router = useRouter();
const router = useRouter() as Router;
if (router.pathname.startsWith('/admin')) {
return <AdminLayout pageProps={pageProps} Component={Component} router={router} />;
}

2
web/pages/admin/config-notify.tsx

@ -12,7 +12,7 @@ import TextFieldWithSubmit, { @@ -12,7 +12,7 @@ import TextFieldWithSubmit, {
import { TEXTFIELD_PROPS_FEDERATION_INSTANCE_URL } from '../../utils/config-constants';
import { ServerStatusContext } from '../../utils/server-status-context';
import { UpdateArgs } from '../../types/config-section';
import isValidUrl from '../utils/urls';
import isValidUrl from '../../utils/urls';
const { Title } = Typography;

31
web/renovate.json

@ -1,11 +1,7 @@ @@ -1,11 +1,7 @@
{
"extends": [
"config:base"
],
"extends": ["config:base"],
"timezone": "America/Los_Angeles",
"schedule": [
"before 8am on Monday"
],
"schedule": ["before 8am on Monday"],
"lockFileMaintenance": {
"enabled": true,
"automerge": true
@ -15,37 +11,26 @@ @@ -15,37 +11,26 @@
},
"packageRules": [
{
"matchUpdateTypes": [
"minor"
],
"matchUpdateTypes": ["minor"],
"matchCurrentVersion": "!/^0/",
"automerge": true
},
{
"matchDepTypes": [
"devDependencies"
],
"matchDepTypes": ["devDependencies"],
"automerge": true,
"major": {
"dependencyDashboardApproval": true
}
},
{
"matchPackagePatterns": [
"*"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"matchPackagePatterns": ["*"],
"matchUpdateTypes": ["minor", "patch"],
"major": {
"dependencyDashboardApproval": true
},
"groupName": "all non-major dependencies",
"groupSlug": "all-minor-patch",
"labels": [
"dependencies"
]
"labels": ["dependencies"]
}
]
}
}

2
web/services/status-service.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import ServerStatus from '../interfaces/server-status.model';
import { ServerStatus } from '../interfaces/server-status.model';
const ENDPOINT = `/api/status`;

8
web/services/websocket-service.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { message } from 'antd';
import { MessageType } from '../interfaces/socket-events';
import { MessageType, SocketEvent } from '../interfaces/socket-events';
interface SocketMessage {
export interface SocketMessage {
type: MessageType;
data: any;
}
@ -15,7 +15,7 @@ export default class WebsocketService { @@ -15,7 +15,7 @@ export default class WebsocketService {
websocketReconnectTimer: ReturnType<typeof setTimeout>;
handleMessage?: (message: SocketMessage) => void;
handleMessage?: (message: SocketEvent) => void;
constructor(accessToken, path) {
this.accessToken = accessToken;
@ -76,7 +76,7 @@ export default class WebsocketService { @@ -76,7 +76,7 @@ export default class WebsocketService {
// Optimization where multiple events can be sent within a
// single websocket message. So split them if needed.
const messages = e.data.split('\n');
let message: SocketMessage;
let message: SocketEvent;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < messages.length; i++) {

7
web/stories/ActionButtonRow.stories.tsx

@ -10,9 +10,10 @@ export default { @@ -10,9 +10,10 @@ export default {
} as ComponentMeta<typeof ActionButtonRow>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const Template: ComponentStory<typeof ActionButtonRow> = args => (
<ActionButtonRow>{args.buttons}</ActionButtonRow>
);
const Template: ComponentStory<typeof ActionButtonRow> = args => {
const { buttons } = args as any;
return <ActionButtonRow>{buttons}</ActionButtonRow>;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const actions = [

1
web/stories/BrowserNotifyModal.stories.tsx

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import BrowserNotifyModal from '../components/modals/BrowserNotifyModal';
import AuthModal from '../components/modals/AuthModal';
const Example = () => (
<div>

2
web/stories/ChatActionMessage.stories.tsx

@ -9,7 +9,7 @@ export default { @@ -9,7 +9,7 @@ export default {
} as ComponentMeta<typeof ChatActionMessage>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const Template: ComponentStory<typeof ChatActionMessage> = args => <ChatActionMessage />;
const Template: ComponentStory<typeof ChatActionMessage> = args => <ChatActionMessage {...args} />;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const Basic = Template.bind({});

2
web/stories/ChatSocialMessage.stories.tsx

@ -9,7 +9,7 @@ export default { @@ -9,7 +9,7 @@ export default {
} as ComponentMeta<typeof ChatSocialMessage>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const Template: ComponentStory<typeof ChatSocialMessage> = args => <ExamChatSocialMessageple />;
const Template: ComponentStory<typeof ChatSocialMessage> = args => <ChatSocialMessage {...args} />;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const Basic = Template.bind({});

3
web/stories/ChatSystemMessage.stories.tsx

@ -8,8 +8,7 @@ export default { @@ -8,8 +8,7 @@ export default {
parameters: {},
} as ComponentMeta<typeof ChatSystemMessage>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const Template: ComponentStory<typeof ChatSystemMessage> = args => <ChatSystemMessage />;
const Template: ComponentStory<typeof ChatSystemMessage> = args => <ChatSystemMessage {...args} />;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const Basic = Template.bind({});

9
web/stories/Color.tsx

@ -25,7 +25,6 @@ export function Color(props) { @@ -25,7 +25,6 @@ export function Color(props) {
const colorDescriptionStyle = {
margin: '5px',
textAlign: 'center',
color: 'gray',
fontSize: '0.8em',
};
@ -33,7 +32,9 @@ export function Color(props) { @@ -33,7 +32,9 @@ export function Color(props) {
return (
<figure style={containerStyle}>
<div style={colorBlockStyle} />
<figcaption style={colorDescriptionStyle}>{color}</figcaption>
<figcaption>
<span style={colorDescriptionStyle}>{color}</span>
</figcaption>
</figure>
);
}
@ -44,8 +45,8 @@ Color.propTypes = { @@ -44,8 +45,8 @@ Color.propTypes = {
const rowStyle = {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
flexDirection: 'row' as 'row',
flexWrap: 'wrap' as 'wrap',
// justifyContent: 'space-around',
alignItems: 'center',
};

3
web/stories/Form.stories.tsx

@ -12,6 +12,7 @@ import { @@ -12,6 +12,7 @@ import {
TreeSelect,
Switch,
} from 'antd';
import { SizeType } from 'antd/lib/config-provider/SizeContext';
const FormExample = () => {
const [componentSize, setComponentSize] = useState('default');
@ -33,7 +34,7 @@ const FormExample = () => { @@ -33,7 +34,7 @@ const FormExample = () => {
size: componentSize,
}}
onValuesChange={onFormLayoutChange}
size={componentSize}
size={componentSize as SizeType}
>
<Form.Item label="Form Size" name="size">
<Radio.Group>

25
web/stories/ImageAsset.tsx

@ -1,6 +1,4 @@ @@ -1,6 +1,4 @@
import PropTypes from 'prop-types';
export function ImageAsset(props) {
export function ImageAsset(props: ImageAssetProps) {
const { name, src } = props;
const containerStyle = {
@ -23,7 +21,7 @@ export function ImageAsset(props) { @@ -23,7 +21,7 @@ export function ImageAsset(props) {
};
const colorDescriptionStyle = {
textAlign: 'center',
textAlign: 'center' as 'center',
color: 'gray',
fontSize: '0.8em',
};
@ -48,19 +46,20 @@ export function ImageAsset(props) { @@ -48,19 +46,20 @@ export function ImageAsset(props) {
);
}
Image.propTypes = {
name: PropTypes.string.isRequired,
};
interface ImageAssetProps {
name: string;
src: string;
}
const rowStyle = {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
flexDirection: 'row' as 'row',
flexWrap: 'wrap' as 'wrap',
// justifyContent: 'space-around',
alignItems: 'center',
};
export function ImageRow(props) {
export function ImageRow(props: ImageRowProps) {
const { images } = props;
return (
@ -72,6 +71,6 @@ export function ImageRow(props) { @@ -72,6 +71,6 @@ export function ImageRow(props) {
);
}
ImageRow.propTypes = {
images: PropTypes.arrayOf(PropTypes.object).isRequired,
};
interface ImageRowProps {
images: ImageAssetProps[];
}

114
web/stories/StyleGuide.stories.mdx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs';
import {Color, ColorRow} from './Color';
import {Image, ImageRow} from './ImageAsset';
import { Color, ColorRow } from './Color';
import { Image, ImageRow } from './ImageAsset';
import Logo from '../assets/images/logo.svg';
import FediverseColor from '../assets/images/fediverse-color.png';
@ -16,73 +16,113 @@ import IsBot from '../assets/images/bot.svg'; @@ -16,73 +16,113 @@ import IsBot from '../assets/images/bot.svg';
`}</style>
export const images = [{
src: Logo,
name: 'Logo',
}, {
src: FediverseColor,
name: 'Fediverse Color',
},{
src: FediverseBlack,
name: 'Fediverse Black',
}, {
src: Moderator,
name: 'Moderator',
}, {
src: IndieAuth,
name: 'IndieAuth',
}, {
src: IsBot,
name: 'Bot Flag',
}];
export const images = [
{
src: Logo,
name: 'Logo',
},
{
src: FediverseColor,
name: 'Fediverse Color',
},
{
src: FediverseBlack,
name: 'Fediverse Black',
},
{
src: Moderator,
name: 'Moderator',
},
{
src: IndieAuth,
name: 'IndieAuth',
},
{
src: IsBot,
name: 'Bot Flag',
},
];
# Colors
<Story name="Colors">
</Story>
<Story name="Colors"></Story>
<ColorRow colors={['theme-primary-color', 'theme-text-color-secondary']} />
## Text
<ColorRow colors={['theme-text-color', 'theme-text-color-secondary', 'theme-link-color']} />
## Backgrounds
<ColorRow colors={['theme-background', 'theme-background-secondary', 'popover-background']} />
## Status
<ColorRow colors={['theme-success-color', 'theme-info-color', 'theme-warning-color', 'theme-error-color']} />
<ColorRow
colors={['theme-success-color', 'theme-info-color', 'theme-warning-color', 'theme-error-color']}
/>
## Gray
<ColorRow colors={['color-owncast-gray-100', 'color-owncast-gray-300', 'color-owncast-gray-500', 'color-owncast-gray-700', 'color-owncast-gray-900']} />
<ColorRow
colors={[
'color-owncast-gray-100',
'color-owncast-gray-300',
'color-owncast-gray-500',
'color-owncast-gray-700',
'color-owncast-gray-900',
]}
/>
## Purple
<ColorRow colors={['color-owncast-purple-100', 'color-owncast-purple-300', 'color-owncast-purple-500', 'color-owncast-purple-700', 'color-owncast-purple-900']} />
<ColorRow
colors={[
'color-owncast-purple-100',
'color-owncast-purple-300',
'color-owncast-purple-500',
'color-owncast-purple-700',
'color-owncast-purple-900',
]}
/>
## Green
<ColorRow colors={['color-owncast-green-100', 'color-owncast-green-300', 'color-owncast-green-500', 'color-owncast-green-700', 'color-owncast-green-900']} />
<ColorRow
colors={[
'color-owncast-green-100',
'color-owncast-green-300',
'color-owncast-green-500',
'color-owncast-green-700',
'color-owncast-green-900',
]}
/>
## Orange
<ColorRow colors={['color-owncast-orange-100', 'color-owncast-orange-300', 'color-owncast-orange-500', 'color-owncast-orange-700', 'color-owncast-orange-900']} />
<ColorRow
colors={[
'color-owncast-orange-100',
'color-owncast-orange-300',
'color-owncast-orange-500',
'color-owncast-orange-700',
'color-owncast-orange-900',
]}
/>
# Font
[Inter font](https://rsms.me/inter/)
<Story name="Fonts">
<Canvas style={{color: 'var(--theme-text-color-secondary)'}}>
<Canvas style={{ color: 'var(--theme-text-color-secondary)' }}>
{getComputedStyle(document.documentElement).getPropertyValue('--theme-font-family')}
</Canvas>
</Story>
# Images
<Story name="Images and Icons">
</Story>
<Story name="Images and Icons"></Story>
<ImageRow images={images} />

66
web/stories/Tabs.stories.tsx

@ -1,66 +0,0 @@ @@ -1,66 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Tabs, Radio } from 'antd';
import { ComponentStory, ComponentMeta } from '@storybook/react';
const { TabPane } = Tabs;
class TabsExample extends React.Component {
constructor(props) {
super(props);
this.state = { size: 'small' };
}
onChange = e => {
this.setState({ size: e.target.value });
};
render() {
const { size } = this.state;
const { type } = this.props;
return (
<div>
<Radio.Group value={size} onChange={this.onChange} style={{ marginBottom: 16 }}>
<Radio.Button value="small">Small</Radio.Button>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="large">Large</Radio.Button>
</Radio.Group>
<Tabs defaultActiveKey="1" type={type} size={size}>
<TabPane tab="Card Tab 1" key="1">
Content of card tab 1
</TabPane>
<TabPane tab="Card Tab 2" key="2">
Content of card tab 2
</TabPane>
<TabPane tab="Card Tab 3" key="3">
Content of card tab 3
</TabPane>
</Tabs>
</div>
);
}
}
export default {
title: 'example/Tabs',
component: Tabs,
} as ComponentMeta<typeof Tabs>;
const Template: ComponentStory<typeof Tabs> = args => <TabsExample {...args} />;
export const Card = Template.bind({});
Card.args = { type: 'card' };
export const Basic = Template.bind({});
Basic.args = { type: '' };
TabsExample.propTypes = {
type: PropTypes.string,
};
TabsExample.defaultProps = {
type: '',
};

5
web/style-definitions/README.md

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
# Style Definitions
Read more about [Style Dictionary](https://amzn.github.io/style-dictionary)
## Add
Add to the `tokens/**/*.yaml` files to add or modify style values.
## Generating
## Generating
Run `npm run build-styles` to regenerate the CSS variables.
Run `npm run build-styles` to regenerate the CSS variables.

32
web/style-definitions/tokens/color/antd-overrides.yaml

@ -2,33 +2,33 @@ @@ -2,33 +2,33 @@
# You can find the variable names to override at:
# https://github.com/ant-design/ant-design/blob/master/components/style/themes/dark.less
text-color:
value: "var(--theme-text-color)"
text-color:
value: 'var(--theme-text-color)'
text-color-secondry:
value: "var(--theme-text-color-secondary)"
value: 'var(--theme-text-color-secondary)'
link-color:
value: "var(--theme-link-color)"
value: 'var(--theme-link-color)'
popover-background:
value: "var(--theme-background)"
value: 'var(--theme-background)'
background-color-light:
value: "var(--theme-background-secondary)"
value: 'var(--theme-background-secondary)'
# These values require explicit colors and cannot take css variables.
primary-color:
value: "{color.owncast.purple.500.value}"
primary-color:
value: '{color.owncast.purple.500.value}'
info-color:
value: "{color.owncast.gray.500.value}"
value: '{color.owncast.gray.500.value}'
success-color:
value: "{color.owncast.green.500.value}"
value: '{color.owncast.green.500.value}'
warning-color:
value: "{color.owncast.orange.500.value}"
value: '{color.owncast.orange.500.value}'
error-color:
value: "{color.owncast.red.500.value}"
value: '{color.owncast.red.500.value}'
purple-base:
value: "{color.owncast.purple.500.value}"
value: '{color.owncast.purple.500.value}'
green-base:
value: "{color.owncast.green.500.value}"
value: '{color.owncast.green.500.value}'
red-base:
value: "{color.owncast.red.500.value}"
value: '{color.owncast.red.500.value}'
orange-base:
value: "{color.owncast.orange.500.value}"
value: '{color.owncast.orange.500.value}'

34
web/style-definitions/tokens/color/default-theme.yaml

@ -5,32 +5,32 @@ @@ -5,32 +5,32 @@
theme:
primary-color:
value: "{color.owncast.purple.500.value}"
comment: "The primary color of the application used for rendering controls."
value: '{color.owncast.purple.500.value}'
comment: 'The primary color of the application used for rendering controls.'
text-color:
value: "{color.owncast.gray.300.value}"
comment: "The color of the text in the application."
value: '{color.owncast.gray.300.value}'
comment: 'The color of the text in the application.'
text-color-secondary:
value: "{color.owncast.gray.500.value}"
value: '{color.owncast.gray.500.value}'
link-color:
value: "{color.owncast.purple.500.value}"
value: '{color.owncast.purple.500.value}'
font-family:
value: "{font.owncast.family.value}"
value: '{font.owncast.family.value}'
background:
value: "{color.owncast.background.value}"
comment: "The main background color of the page."
value: '{color.owncast.background.value}'
comment: 'The main background color of the page.'
background-secondary:
value: "{color.owncast.background-secondary.value}"
comment: "A secondary background color used in sections and controls."
value: '{color.owncast.background-secondary.value}'
comment: 'A secondary background color used in sections and controls.'
rounded-corners:
value: "5px"
comment: "The radius of rounded corners used in places."
value: '5px'
comment: 'The radius of rounded corners used in places.'
success-color:
value: "{color.owncast.green.500.value}"
value: '{color.owncast.green.500.value}'
info-color:
value: "{color.owncast.purple.300.value}"
value: '{color.owncast.purple.300.value}'
warning-color:
value: "{color.owncast.orange.500.value}"
value: '{color.owncast.orange.500.value}'
error-color:
value: "{color.owncast.red.500.value}"
value: '{color.owncast.red.500.value}'

92
web/style-definitions/tokens/color/owncast-colors.yaml

@ -4,79 +4,79 @@ color: @@ -4,79 +4,79 @@ color:
owncast:
purple:
100:
value: "#f4ebff"
value: '#f4ebff'
300:
value: "#d6bbfb"
value: '#d6bbfb'
500:
value: "#9e77ed"
value: '#9e77ed'
700:
value: "#6941c6"
value: '#6941c6'
900:
value: "#42307d"
value: '#42307d'
green:
100:
value: "#d15ad5"
value: '#d15ad5'
300:
value: "#6ce9a6"
value: '#6ce9a6'
500:
value: "#12b76a"
value: '#12b76a'
700:
value: "#027a48"
value: '#027a48'
900:
value: "#054f31"
value: '#054f31'
red:
100:
value: "#fee4e2"
value: '#fee4e2'
300:
value: "#fda29b"
value: '#fda29b'
500:
value: "#f04438"
value: '#f04438'
700:
value: "#b42318"
value: '#b42318'
900:
value: "#7a271a"
value: '#7a271a'
orange:
100:
value: "#fef0c7"
value: '#fef0c7'
300:
value: "#fec84b"
value: '#fec84b'
500:
value: "#f79009"
value: '#f79009'
700:
value: "#b54708"
value: '#b54708'
900:
value: "#93370d"
value: '#93370d'
gray:
100:
value: "#f2f4f7"
value: '#f2f4f7'
300:
value: "#d0d5dd"
value: '#d0d5dd'
500:
value: "#667085"
value: '#667085'
700:
value: "#344054"
value: '#344054'
900:
value: "#101828"
value: '#101828'
logo:
purple:
value: "rgba(120, 113, 255, 1)"
value: 'rgba(120, 113, 255, 1)'
pink:
value: "rgba(201, 139, 254, 1)"
value: 'rgba(201, 139, 254, 1)'
blue:
value: "rgba(32, 134, 225, 1)"
value: 'rgba(32, 134, 225, 1)'
background:
value: "rgba(27, 26, 38, 1)"
value: 'rgba(27, 26, 38, 1)'
background-secondary:
value: "rgba(22, 21, 31, 1)"
value: 'rgba(22, 21, 31, 1)'
font:
owncast:
owncast:
family:
value: "'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
@ -85,28 +85,28 @@ font: @@ -85,28 +85,28 @@ font:
# Values used in the admin and should be migrated to variables or removed.
# See ant-overrides.scss.
owncast-purple:
value: "{color.owncast.logo.purple.value}"
owncast-purple-25:
value: "rgba(120, 113, 255, 0.25)"
value: '{color.owncast.logo.purple.value}'
owncast-purple-25:
value: 'rgba(120, 113, 255, 0.25)'
owncast-purple-50:
value: "rgba(120, 113, 255, 0.5)"
value: 'rgba(120, 113, 255, 0.5)'
online-color:
value: "#73dd3f"
value: '#73dd3f'
offline-color:
value: "#999"
value: '#999'
pink:
value: "{color.owncast.logo.pink.value}"
value: '{color.owncast.logo.pink.value}'
purple:
value: "{color.owncast.purple.500.value}"
value: '{color.owncast.purple.500.value}'
blue:
value: "{color.owncast.logo.blue.value}"
value: '{color.owncast.logo.blue.value}'
white-88:
value: "{color.owncast.gray.500.value}"
value: '{color.owncast.gray.500.value}'
purple-dark:
value: "{color.owncast.purple.900.value}"
value: '{color.owncast.purple.900.value}'
default-link-color:
value: "{color.owncast.purple.700.value}"
value: '{color.owncast.purple.700.value}'
default-bg-color:
value: "{color.owncast.background.value}"
value: '{color.owncast.background.value}'
default-text-color:
value: "{color.owncast.gray.100.value}"
value: '{color.owncast.gray.100.value}'

1
web/styles/ant-overrides.scss

@ -356,7 +356,6 @@ textarea.ant-input { @@ -356,7 +356,6 @@ textarea.ant-input {
opacity: 0.75;
}
// SELECT
.ant-select-dropdown {
background-color: var(--black);

12
web/styles/config-storage.scss

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
// styles for Storage config section
// styles for Storage config section
.edit-storage-container {
padding: 1em;
@ -21,9 +20,7 @@ @@ -21,9 +20,7 @@
}
}
.edit-server-details-container {
// Do something special for the stream key field
.field-streamkey-container {
margin-bottom: 1.5em;
@ -33,7 +30,7 @@ @@ -33,7 +30,7 @@
.left-side {
display: flex;
flex-direction: row;
align-items: flex-start;
align-items: flex-start;
}
.textfield-with-submit-container {
@ -43,7 +40,7 @@ @@ -43,7 +40,7 @@
.streamkey-actions {
white-space: nowrap;
button {
margin: .25em;
margin: 0.25em;
}
@media (max-width: 800px) {
margin-top: 2em;
@ -54,5 +51,4 @@ @@ -54,5 +51,4 @@
.advanced-settings {
max-width: 800px;
}
}
}

20
web/styles/form-misc-elements.scss

@ -19,40 +19,37 @@ @@ -19,40 +19,37 @@
flex-direction: row;
justify-content: flex-start;
align-items: center;
font-size: .75rem;
font-size: 0.75rem;
.status-icon {
display: inline-block;
margin-right: .5em;
margin-right: 0.5em;
}
}
/* TIP CONTAINER BASE */
.field-tip {
font-size: .8em;
font-size: 0.8em;
color: var(--white-50);
}
/*
Ideal for wrapping each Textfield on a page with many text fields in a row. This div will alternate colors and look like a table.
*/
.field-container {
padding: .85em 0 .5em;
padding: 0.85em 0 0.5em;
}
/* SEGMENT SLIDER GROUP WITH SELECTED NOTE, OR STATUS */
.segment-slider-container {
width: 100%;
margin: auto;
padding: 1em 2em .75em;
padding: 1em 2em 0.75em;
background-color: var(--owncast-purple-25);
border-radius: var(--container-border-radius);
.status-container {
width: 100%;
margin: .5em auto;
margin: 0.5em auto;
text-align: center;
}
@ -60,7 +57,7 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This @@ -60,7 +57,7 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This
width: 100%;
margin: 3em auto 0;
text-align: center;
font-size: .75em;
font-size: 0.75em;
line-height: normal;
color: var(--white);
padding: 1em;
@ -69,7 +66,6 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This @@ -69,7 +66,6 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This
}
}
.segment-tip {
width: 10em;
text-align: center;
@ -81,4 +77,4 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This @@ -81,4 +77,4 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This
margin-top: 8px;
text-align: justify;
line-height: 1.4em;
}
}

22
web/styles/form-textfields.scss

@ -19,7 +19,7 @@ Base styles for @@ -19,7 +19,7 @@ Base styles for
padding-right: 1.25em;
text-align: right;
width: var(--form-label-container-width);
margin: .2em 0;
margin: 0.2em 0;
}
.formfield-label {
font-weight: 500;
@ -35,7 +35,7 @@ Base styles for @@ -35,7 +35,7 @@ Base styles for
&::before {
content: '*';
display: inline-block;
margin-right: .25em;
margin-right: 0.25em;
color: var(--ant-error);
}
}
@ -54,7 +54,7 @@ Base styles for @@ -54,7 +54,7 @@ Base styles for
}
.status-container {
margin: .25em;
margin: 0.25em;
width: 100%;
display: block;
&.empty {
@ -62,9 +62,9 @@ Base styles for @@ -62,9 +62,9 @@ Base styles for
visibility: visible;
}
}
.field-tip {
margin: .5em .5em;
margin: 0.5em 0.5em;
}
@media (max-width: 800px) {
@ -103,7 +103,7 @@ Base styles for @@ -103,7 +103,7 @@ Base styles for
justify-content: flex-start;
.label-spacer {
width: var(--form-label-container-width);
width: var(--form-label-container-width);
}
.lower-content {
display: flex;
@ -117,7 +117,7 @@ Base styles for @@ -117,7 +117,7 @@ Base styles for
width: 100%;
}
.status-container {
margin: .5em;
margin: 0.5em;
&.empty {
display: none;
}
@ -125,7 +125,7 @@ Base styles for @@ -125,7 +125,7 @@ Base styles for
}
.update-button-container {
visibility: hidden;
margin: .25em 0;
margin: 0.25em 0;
}
}
@ -137,7 +137,6 @@ Base styles for @@ -137,7 +137,6 @@ Base styles for
}
}
@media (max-width: 800px) {
.label-spacer {
display: none;
@ -145,12 +144,11 @@ Base styles for @@ -145,12 +144,11 @@ Base styles for
}
}
/* TOGGLE SWITCH CONTAINER BASE */
.toggleswitch-container {
margin: 2em 0 1em;
.label-side {
.label-side {
margin-top: 0;
}
@ -159,7 +157,7 @@ Base styles for @@ -159,7 +157,7 @@ Base styles for
flex-direction: row;
justify-content: flex-start;
align-items: center;
.status-container {
width: auto;
margin: 0 0 0 1em;

3
web/styles/globals.scss

@ -5,9 +5,8 @@ @@ -5,9 +5,8 @@
// --header-h: 64px;
--chat-w: 300px;
--chat-input-h: 40.5px;
}
}
html,
body {
padding: 0;

5
web/styles/pages.scss

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
// misc styling for various /pages
// .help-page {
// .ant-result-image {
// height: 100px;
@ -11,9 +10,9 @@ @@ -11,9 +10,9 @@
// }
// }
.upgrade-page {
h2,h3 {
h2,
h3 {
color: var(--pink);
font-size: 1.25em;
}

11
web/styles/theme.less

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
// Do not edit directly
// Generated on Sat, 07 May 2022 17:24:18 GMT
@ -20,7 +19,9 @@ @@ -20,7 +19,9 @@
@theme-text-color: #d0d5dd; // The color of the text in the application.
@theme-text-color-secondary: #667085;
@theme-link-color: #9e77ed;
@theme-font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
@theme-font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
@theme-background: #1b1a26; // The main background color of the page.
@theme-background-secondary: #16151f; // A secondary background color used in sections and controls.
@theme-rounded-corners: 5px; // The radius of rounded corners.
@ -58,7 +59,9 @@ @@ -58,7 +59,9 @@
@color-owncast-logo-blue: #2086e1;
@color-owncast-background: #1b1a26;
@color-owncast-background-secondary: #16151f;
@font-owncast-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
@font-owncast-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
@owncast-purple: #7871ff;
@owncast-purple-25: rgba(120, 113, 255, 0.25);
@owncast-purple-50: rgba(120, 113, 255, 0.5);
@ -71,4 +74,4 @@ @@ -71,4 +74,4 @@
@purple-dark: #42307d;
@default-link-color: #6941c6;
@default-bg-color: #1b1a26;
@default-text-color: #f2f4f7;
@default-text-color: #f2f4f7;

16
web/tsconfig.json

@ -1,11 +1,7 @@ @@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@ -19,12 +15,6 @@ @@ -19,12 +15,6 @@
"jsx": "preserve",
"incremental": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

10
web/utils/hook-windowresize.tsx

@ -17,15 +17,15 @@ export default function useWindowSize() { @@ -17,15 +17,15 @@ export default function useWindowSize() {
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
window.addEventListener('resize', handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;

14
web/utils/urls.ts

@ -5,11 +5,15 @@ export default function isValidUrl(url: string): boolean { @@ -5,11 +5,15 @@ export default function isValidUrl(url: string): boolean {
const validProtocols = ['http:', 'https:'];
try {
const validationObject = new URL(url);
if (validationObject.protocol === '' || validationObject.hostname === '' || !validProtocols.includes(validationObject.protocol)) {
return false;
}
} catch(e) {
const validationObject = new URL(url);
if (
validationObject.protocol === '' ||
validationObject.hostname === '' ||
!validProtocols.includes(validationObject.protocol)
) {
return false;
}
} catch (e) {
return false;
}

Loading…
Cancel
Save