mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
reorganize styles and pages (wip); update readme
This commit is contained in:
parent
8ddd780281
commit
ff51c168ff
@ -1,6 +1,7 @@
|
|||||||
import 'antd/dist/antd.css';
|
import 'antd/dist/antd.css';
|
||||||
import '../styles/colors.scss';
|
import '../styles/colors.scss';
|
||||||
import '../styles/globals.scss';
|
import '../styles/globals.scss';
|
||||||
|
import '../styles/ant-overrides.scss';
|
||||||
|
|
||||||
import '../styles/home.scss';
|
import '../styles/home.scss';
|
||||||
import '../styles/chat.scss';
|
import '../styles/chat.scss';
|
||||||
|
@ -1,5 +1,49 @@
|
|||||||
# Config
|
# About the Config editing section
|
||||||
|
|
||||||
|
An adventure with React, React Hooks and Ant Design forms.
|
||||||
|
|
||||||
|
## General data flow in this React app
|
||||||
|
|
||||||
|
### First things to note
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
- The `ServerStatusContext` also provides a method for components to update the serverConfig state, called `setFieldInConfigState()`.
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
- Each top field of the serverConfig has its own API update endpoint.
|
||||||
|
|
||||||
|
### Form Flow
|
||||||
|
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.
|
||||||
|
|
||||||
|
There are also a variety of other local states to manage the display of error/success messaging.
|
||||||
|
|
||||||
|
## Using Ant's `<Form>` with `form-textfield`.
|
||||||
|
You may see that a couple of pages (currently Public Details and Server Details page), is mainly a grouping of similar Text fields.
|
||||||
|
|
||||||
|
`const [form] = Form.useForm();`
|
||||||
|
`form.setFieldsValue(initialValues);`
|
||||||
|
|
||||||
|
|
||||||
|
A special `TextField` component was created to be used with form.
|
||||||
|
|
||||||
|
|
||||||
|
## Potential Optimizations
|
||||||
|
|
||||||
|
Looking back at the pages with `<Form>` + `form-textfield`, t
|
||||||
|
|
||||||
|
This pattern might be overly engineered.
|
||||||
|
|
||||||
|
There are also a few patterns across all the form groups that repeat quite a bit. Perhaps these patterns could be consolidated into a custom hook that could handle all the steps.
|
||||||
|
|
||||||
TODO: explain how to use <Form> and how the custom `form-xxxx` components work together.
|
TODO: explain how to use <Form> and how the custom `form-xxxx` components work together.
|
||||||
|
|
||||||
@ -49,3 +93,11 @@ TODO:
|
|||||||
- fix social handles icon in table
|
- fix social handles icon in table
|
||||||
- consolidate things into 1 page?
|
- consolidate things into 1 page?
|
||||||
- things could use smaller font?
|
- things could use smaller font?
|
||||||
|
- maybe convert common form pattern to custom hook?
|
||||||
|
|
||||||
|
|
||||||
|
Possibly over engineered
|
||||||
|
|
||||||
|
https://uxcandy.co/demo/label_pro/preview/demo_2/pages/forms/form-elements.html
|
||||||
|
|
||||||
|
https://www.bootstrapdash.com/demo/corona/jquery/template/modern-vertical/pages/forms/basic_elements.html
|
@ -2,16 +2,18 @@ import React, { useState, useContext, useEffect } from 'react';
|
|||||||
import { Typography, Table, Button, Modal, Input } from 'antd';
|
import { Typography, Table, Button, Modal, Input } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import SocialDropdown from './components/config/social-icons-dropdown';
|
import SocialDropdown from './social-icons-dropdown';
|
||||||
import { fetchData, NEXT_PUBLIC_API_HOST, SOCIAL_PLATFORMS_LIST } from '../utils/apis';
|
import { fetchData, NEXT_PUBLIC_API_HOST, SOCIAL_PLATFORMS_LIST } from '../../../utils/apis';
|
||||||
import { ServerStatusContext } from '../utils/server-status-context';
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
import { API_SOCIAL_HANDLES, postConfigUpdateToAPI, RESET_TIMEOUT, SUCCESS_STATES, DEFAULT_SOCIAL_HANDLE, OTHER_SOCIAL_HANDLE_OPTION } from './components/config/constants';
|
import { API_SOCIAL_HANDLES, postConfigUpdateToAPI, RESET_TIMEOUT, SUCCESS_STATES, DEFAULT_SOCIAL_HANDLE, OTHER_SOCIAL_HANDLE_OPTION } from './constants';
|
||||||
import { SocialHandle } from '../types/config-section';
|
import { SocialHandle } from '../../../types/config-section';
|
||||||
import {isValidUrl} from '../utils/urls';
|
import {isValidUrl} from '../../../utils/urls';
|
||||||
|
|
||||||
|
import configStyles from '../../../styles/config-pages.module.scss';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export default function ConfigSocialLinks() {
|
export default function EditSocialLinks() {
|
||||||
const [availableIconsList, setAvailableIconsList] = useState([]);
|
const [availableIconsList, setAvailableIconsList] = useState([]);
|
||||||
const [currentSocialHandles, setCurrentSocialHandles] = useState([]);
|
const [currentSocialHandles, setCurrentSocialHandles] = useState([]);
|
||||||
|
|
||||||
@ -228,14 +230,14 @@ export default function ConfigSocialLinks() {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-social-links">
|
<div className={configStyles.socialLinksEditor}>
|
||||||
<Title level={2}>Social Links</Title>
|
<Title level={2}>Social Links</Title>
|
||||||
<p>Add all your social media handles and links to your other profiles here.</p>
|
<p>Add all your social media handles and links to your other profiles here.</p>
|
||||||
|
|
||||||
{statusMessage}
|
{statusMessage}
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
className="variants-table"
|
className="dataTable"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
rowKey={record => record.url}
|
rowKey={record => record.url}
|
@ -155,10 +155,9 @@ export default function TextField(props: TextFieldProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`textfield-container type-${type}`}>
|
<div className={`textfield-container type-${type}`}>
|
||||||
|
<div className="textfield-label">{label}</div>
|
||||||
<div className="textfield">
|
<div className="textfield">
|
||||||
<InfoTip tip={tip} />
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={label}
|
|
||||||
name={fieldName}
|
name={fieldName}
|
||||||
hasFeedback
|
hasFeedback
|
||||||
validateStatus={submitStatus}
|
validateStatus={submitStatus}
|
||||||
@ -176,8 +175,8 @@ export default function TextField(props: TextFieldProps) {
|
|||||||
{...fieldProps}
|
{...fieldProps}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<InfoTip tip={tip} />
|
||||||
|
|
||||||
{ hasChanged ? <Button type="primary" size="small" className="submit-button" onClick={handleSubmit}>Update</Button> : null }
|
{ hasChanged ? <Button type="primary" size="small" className="submit-button" onClick={handleSubmit}>Update</Button> : null }
|
||||||
|
|
||||||
|
@ -141,12 +141,14 @@ export default function MainLayout(props) {
|
|||||||
<Menu.Item key="config-public-details">
|
<Menu.Item key="config-public-details">
|
||||||
<Link href="/config-public-details">Public Details</Link>
|
<Link href="/config-public-details">Public Details</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item key="config-social-items">
|
||||||
|
<Link href="/config-social-items">Social items</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item key="config-page-content">
|
<Menu.Item key="config-page-content">
|
||||||
<Link href="/config-page-content">Custom page content</Link>
|
<Link href="/config-page-content">Custom page content</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="config-social-links">
|
|
||||||
<Link href="/config-social-links">Social links</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="config-server-details">
|
<Menu.Item key="config-server-details">
|
||||||
<Link href="/config-server-details">Server Details</Link>
|
<Link href="/config-server-details">Server Details</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
@ -4,12 +4,11 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
import TextField, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './components/config/form-textfield';
|
import TextField, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './components/config/form-textfield';
|
||||||
|
|
||||||
import EditInstanceTags from './components/config/edit-tags';
|
|
||||||
import EditDirectoryDetails from './components/config/edit-directory';
|
|
||||||
|
|
||||||
import { ServerStatusContext } from '../utils/server-status-context';
|
import { ServerStatusContext } from '../utils/server-status-context';
|
||||||
import { TEXTFIELD_DEFAULTS, postConfigUpdateToAPI } from './components/config/constants';
|
import { TEXTFIELD_DEFAULTS, postConfigUpdateToAPI } from './components/config/constants';
|
||||||
|
|
||||||
|
import configStyles from '../styles/config-pages.module.scss';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export default function PublicFacingDetails() {
|
export default function PublicFacingDetails() {
|
||||||
@ -29,11 +28,6 @@ export default function PublicFacingDetails() {
|
|||||||
form.setFieldsValue(initialValues);
|
form.setFieldsValue(initialValues);
|
||||||
}, [instanceDetails]);
|
}, [instanceDetails]);
|
||||||
|
|
||||||
// const handleResetValue = (fieldName: string) => {
|
|
||||||
// const defaultValue = defaultFields[fieldName] && defaultFields[fieldName].defaultValue || '';
|
|
||||||
|
|
||||||
// form.setFieldsValue({ [fieldName]: initialValues[fieldName] || defaultValue });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if instanceUrl is empty, we should also turn OFF the `enabled` field of directory.
|
// if instanceUrl is empty, we should also turn OFF the `enabled` field of directory.
|
||||||
const handleSubmitInstanceUrl = () => {
|
const handleSubmitInstanceUrl = () => {
|
||||||
@ -49,17 +43,16 @@ export default function PublicFacingDetails() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extraProps = {
|
const extraProps = {
|
||||||
// handleResetValue,
|
|
||||||
initialValues,
|
initialValues,
|
||||||
configPath: 'instanceDetails',
|
configPath: 'instanceDetails',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-public-details-form">
|
<>
|
||||||
<Title level={2}>Edit your public facing instance details</Title>
|
<Title level={2}>Edit your public facing instance details</Title>
|
||||||
|
|
||||||
<div className="config-public-details-container">
|
<div className={configStyles.publicDetailsContainer}>
|
||||||
<div className="text-fields">
|
<div className={configStyles.textFieldsSection}>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
@ -78,20 +71,13 @@ export default function PublicFacingDetails() {
|
|||||||
<TextField fieldName="summary" type={TEXTFIELD_TYPE_TEXTAREA} {...extraProps} />
|
<TextField fieldName="summary" type={TEXTFIELD_TYPE_TEXTAREA} {...extraProps} />
|
||||||
<TextField fieldName="logo" {...extraProps} />
|
<TextField fieldName="logo" {...extraProps} />
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Link href="/admin/config-page-content">
|
<Link href="/admin/config-page-content">
|
||||||
<a>this page!</a>
|
<a>Edit your extra page content here.</a>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="misc-fields">
|
|
||||||
{/* add social handles comp
|
|
||||||
<br/>
|
|
||||||
add tags comp */}
|
|
||||||
<EditInstanceTags />
|
|
||||||
|
|
||||||
<EditDirectoryDetails />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
web/pages/config-social-items.tsx
Normal file
20
web/pages/config-social-items.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import EditSocialLinks from './components/config/edit-social-links';
|
||||||
|
import EditInstanceTags from './components/config/edit-tags';
|
||||||
|
import EditDirectoryDetails from './components/config/edit-directory';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function ConfigSocialThings() {
|
||||||
|
return (
|
||||||
|
<div className="config-social-items">
|
||||||
|
<Title level={2}>Social Items</Title>
|
||||||
|
|
||||||
|
<EditDirectoryDetails />
|
||||||
|
<EditSocialLinks />
|
||||||
|
<EditInstanceTags />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
95
web/styles/ant-overrides.scss
Normal file
95
web/styles/ant-overrides.scss
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// GENERAL ANT OVERRIDES
|
||||||
|
.ant-layout,
|
||||||
|
.ant-layout-footer,
|
||||||
|
.ant-menu,
|
||||||
|
.ant-menu.ant-menu-dark {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||||
|
background-color: var(--owncast-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LAYOUT
|
||||||
|
.ant-layout-header,
|
||||||
|
.ant-layout-sider {
|
||||||
|
background-color: #07050d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MENU
|
||||||
|
.ant-menu-dark .ant-menu-inline.ant-menu-sub {
|
||||||
|
// background-color: rgba(255,255,255,.05);
|
||||||
|
background-color: #140028;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CARD
|
||||||
|
.ant-card {
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INPUT
|
||||||
|
.ant-input-affix-wrapper {
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: rgba(255,255,255,.1);
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUTTON
|
||||||
|
.ant-btn-primary:hover, .ant-btn-primary:focus {
|
||||||
|
background-color: white;
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
.ant-btn.ant-btn-primary:focus {
|
||||||
|
border-color: white;
|
||||||
|
|
||||||
|
}
|
||||||
|
.ant-input-affix-wrapper,
|
||||||
|
.ant-btn {
|
||||||
|
transition-delay: 0s;
|
||||||
|
transition-duration: 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TABLE
|
||||||
|
.ant-table-thead > tr > th,
|
||||||
|
.ant-table-small .ant-table-thead > tr > th {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODAL
|
||||||
|
.ant-modal-content {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.ant-modal-header {
|
||||||
|
background-color: #1c173d;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
}
|
||||||
|
.ant-modal-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.ant-modal-body {
|
||||||
|
background-color: #33333c;
|
||||||
|
}
|
||||||
|
.ant-modal-footer {
|
||||||
|
background-color: #222229;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT
|
||||||
|
.ant-select-dropdown {
|
||||||
|
background-color: #334;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SLIDER
|
||||||
|
.ant-slider-with-marks {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
.ant-slider-mark-text {
|
||||||
|
font-size: .85em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
33
web/styles/config-pages.module.scss
Normal file
33
web/styles/config-pages.module.scss
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.publicDetailsContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.textFieldsSection {
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.socialLinksEditor {
|
||||||
|
width: 20rem;
|
||||||
|
margin: 2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.tag-editor-container,
|
||||||
|
.config-directory-details-form {
|
||||||
|
border-radius: 1em;
|
||||||
|
background-color: rgba(128,99,255,.1);
|
||||||
|
padding: 1.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// common?
|
||||||
|
.dataTable {
|
||||||
|
|
||||||
|
}
|
@ -26,13 +26,6 @@
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-slider-with-marks {
|
|
||||||
margin-right: 2em;
|
|
||||||
}
|
|
||||||
.ant-slider-mark-text {
|
|
||||||
font-size: .85em;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-message {
|
.status-message {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
@import "~antd/dist/antd.dark";
|
@import "~antd/dist/antd.dark";
|
||||||
|
|
||||||
$owncast-purple: rgba(90,103,216,1);
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -37,84 +35,12 @@ code {
|
|||||||
color: var(--owncast-purple);
|
color: var(--owncast-purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
|
||||||
background-color: $owncast-purple;
|
|
||||||
}
|
|
||||||
// GENERAL ANT FORM OVERRIDES
|
|
||||||
.ant-layout,
|
|
||||||
.ant-layout-footer,
|
|
||||||
.ant-menu,
|
|
||||||
.ant-menu.ant-menu-dark {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.ant-layout-header,
|
|
||||||
.ant-layout-sider {
|
|
||||||
background-color: #07050d;
|
|
||||||
}
|
|
||||||
.ant-menu-dark .ant-menu-inline.ant-menu-sub {
|
|
||||||
// background-color: rgba(255,255,255,.05);
|
|
||||||
background-color: #140028;
|
|
||||||
}
|
|
||||||
.ant-card {
|
|
||||||
border-radius: .5em;
|
|
||||||
}
|
|
||||||
.ant-input-affix-wrapper {
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: rgba(255,255,255,.1);
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-btn-primary:hover, .ant-btn-primary:focus {
|
|
||||||
background-color: white;
|
|
||||||
color: #40a9ff;
|
|
||||||
}
|
|
||||||
.ant-btn.ant-btn-primary:focus {
|
|
||||||
border-color: white;
|
|
||||||
|
|
||||||
}
|
|
||||||
.ant-input-affix-wrapper,
|
|
||||||
.ant-btn {
|
|
||||||
transition-delay: 0s;
|
|
||||||
transition-duration: 0.15s;
|
|
||||||
}
|
|
||||||
.ant-table-thead > tr > th,
|
|
||||||
.ant-table-small .ant-table-thead > tr > th {
|
|
||||||
background-color: #000;
|
|
||||||
}
|
|
||||||
.ant-modal-content {
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.ant-modal-header {
|
|
||||||
background-color: #1c173d;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
}
|
|
||||||
.ant-modal-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
.ant-modal-body {
|
|
||||||
background-color: #33333c;
|
|
||||||
}
|
|
||||||
.ant-modal-footer {
|
|
||||||
background-color: #222229;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.ant-select-dropdown {
|
|
||||||
background-color: #334;
|
|
||||||
}
|
|
||||||
.rc-virtual-list-scrollbar {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// markdown editor overrides
|
// markdown editor overrides
|
||||||
|
|
||||||
|
.rc-virtual-list-scrollbar {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
.rc-md-editor {
|
.rc-md-editor {
|
||||||
// Set the background color of the preview container
|
// Set the background color of the preview container
|
||||||
.editor-container {
|
.editor-container {
|
||||||
@ -126,7 +52,7 @@ code {
|
|||||||
.markdown-editor-preview-pane {
|
.markdown-editor-preview-pane {
|
||||||
// color:lightgrey;
|
// color:lightgrey;
|
||||||
a {
|
a {
|
||||||
color: $owncast-purple;
|
color: var(--owncast-purple);;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
|
@ -74,7 +74,7 @@ interface FetchOptions {
|
|||||||
auth?: boolean;
|
auth?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchData(url: string, options?: object) {
|
export async function fetchData(url: string, options?: FetchOptions) {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
method = 'GET',
|
method = 'GET',
|
||||||
@ -129,23 +129,6 @@ export async function getGithubRelease() {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a request to the server status API and the Github releases API
|
|
||||||
// and return a release if it's newer than the server version.
|
|
||||||
export async function upgradeVersionAvailable(currentVersion) {
|
|
||||||
const recentRelease = await getGithubRelease();
|
|
||||||
let recentReleaseVersion = recentRelease.tag_name;
|
|
||||||
|
|
||||||
if (recentReleaseVersion.substr(0, 1) === 'v') {
|
|
||||||
recentReleaseVersion = recentReleaseVersion.substr(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!upToDate(currentVersion, recentReleaseVersion)) {
|
|
||||||
return recentReleaseVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stolen from https://gist.github.com/prenagha/98bbb03e27163bc2f5e4
|
// Stolen from https://gist.github.com/prenagha/98bbb03e27163bc2f5e4
|
||||||
const VPAT = /^\d+(\.\d+){0,2}$/;
|
const VPAT = /^\d+(\.\d+){0,2}$/;
|
||||||
function upToDate(local, remote) {
|
function upToDate(local, remote) {
|
||||||
@ -172,5 +155,21 @@ function upToDate(local, remote) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return local >= remote;
|
return local >= remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a request to the server status API and the Github releases API
|
||||||
|
// and return a release if it's newer than the server version.
|
||||||
|
export async function upgradeVersionAvailable(currentVersion) {
|
||||||
|
const recentRelease = await getGithubRelease();
|
||||||
|
let recentReleaseVersion = recentRelease.tag_name;
|
||||||
|
|
||||||
|
if (recentReleaseVersion.substr(0, 1) === 'v') {
|
||||||
|
recentReleaseVersion = recentReleaseVersion.substr(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!upToDate(currentVersion, recentReleaseVersion)) {
|
||||||
|
return recentReleaseVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user