fixes for various admin issues (#181)
* up max char count for variant name to fix https://github.com/owncast/owncast/issues/1037 * max widthing the line chart canvas size so it scales with the page. fixes - https://github.com/owncast/owncast/issues/842 - https://github.com/owncast/owncast/issues/1024 * A fix to make Storage Endpoint URL validation have better feedback. - give the field a type of "url" - give the field a pattern to check - have native browser handle the validation - if the field is invalid, use :invalid selector to turn the text red on blur. fixes: https://github.com/owncast/owncast/issues/1000 * a fix for https://github.com/owncast/owncast/issues/874 * - fixes for https://github.com/owncast/owncast/issues/972 Add optional prop to text field to trim() whitespaces from field. Apply logic to mostly url fields. - move textfield blur if invalid turn red to globaal * - a fix for bug: https://github.com/owncast/owncast/issues/998 don't return null if platform name not found because its custom. - clean up react key problem on socialhandles table * fix react key issue on Actions table * fix for https://github.com/owncast/owncast/issues/1008 to display 'other' field when editing an item not in predefined social list * clean up other potential react key warnings * Prettified Code! Co-authored-by: gingervitis <gingervitis@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
OTHER_SOCIAL_HANDLE_OPTION,
|
||||
} from '../../utils/config-constants';
|
||||
import { SocialHandle, UpdateArgs } from '../../types/config-section';
|
||||
import isValidUrl from '../../utils/urls';
|
||||
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls';
|
||||
import TextField from './form-textfield';
|
||||
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
|
||||
import FormStatusIndicator from './form-status-indicator';
|
||||
@@ -62,6 +62,10 @@ export default function EditSocialLinks() {
|
||||
}
|
||||
};
|
||||
|
||||
const isPredefinedSocial = (platform: string) => {
|
||||
return availableIconsList.find(item => item.key === platform) || false;
|
||||
};
|
||||
|
||||
const selectedOther =
|
||||
modalDataState.platform !== '' &&
|
||||
!availableIconsList.find(item => item.key === modalDataState.platform);
|
||||
@@ -172,9 +176,18 @@ export default function EditSocialLinks() {
|
||||
key: 'combo',
|
||||
render: (data, record) => {
|
||||
const { platform, url } = record;
|
||||
const platformInfo = availableIconsList.find(item => item.key === platform);
|
||||
const platformInfo = isPredefinedSocial(platform);
|
||||
|
||||
// custom platform case
|
||||
if (!platformInfo) {
|
||||
return platform;
|
||||
return (
|
||||
<div className="social-handle-cell">
|
||||
<p className="option-label">
|
||||
<strong>{platform}</strong>
|
||||
<span className="handle-url" title={url}>{url}</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { icon, platform: platformName } = platformInfo;
|
||||
const iconUrl = NEXT_PUBLIC_API_HOST + `${icon.slice(1)}`;
|
||||
@@ -201,9 +214,13 @@ export default function EditSocialLinks() {
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const platformInfo = currentSocialHandles[index];
|
||||
setEditId(index);
|
||||
setModalDataState({ ...currentSocialHandles[index] });
|
||||
setModalDataState({ ...platformInfo });
|
||||
setDisplayModal(true);
|
||||
if (!isPredefinedSocial(platformInfo.platform)) {
|
||||
setDisplayOther(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
@@ -251,7 +268,7 @@ export default function EditSocialLinks() {
|
||||
className="social-handles-table"
|
||||
pagination={false}
|
||||
size="small"
|
||||
rowKey={record => record.url}
|
||||
rowKey={record => `${record.platform}-${record.url}`}
|
||||
columns={socialHandlesColumns}
|
||||
dataSource={currentSocialHandles}
|
||||
/>
|
||||
@@ -278,6 +295,9 @@ export default function EditSocialLinks() {
|
||||
placeholder={PLACEHOLDERS[modalDataState.platform] || 'Url to page'}
|
||||
value={modalDataState.url}
|
||||
onChange={handleUrlChange}
|
||||
useTrim
|
||||
type="url"
|
||||
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
||||
/>
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
|
||||
@@ -41,6 +41,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
|
||||
apiPath,
|
||||
configPath = '',
|
||||
initialValue,
|
||||
useTrim,
|
||||
...textFieldProps // rest of props
|
||||
} = props;
|
||||
|
||||
@@ -70,7 +71,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
|
||||
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
|
||||
const handleChange = ({ fieldName: changedFieldName, value: changedValue }: UpdateArgs) => {
|
||||
if (onChange) {
|
||||
onChange({ fieldName: changedFieldName, value: changedValue });
|
||||
onChange({ fieldName: changedFieldName, value: useTrim ? changedValue.trim() : changedValue });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ export interface TextFieldProps {
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
maxLength?: number;
|
||||
pattern?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
status?: StatusState;
|
||||
tip?: string;
|
||||
type?: string;
|
||||
useTrim?: boolean;
|
||||
value?: string | number;
|
||||
onBlur?: FieldUpdaterFunc;
|
||||
onChange?: FieldUpdaterFunc;
|
||||
@@ -42,20 +44,21 @@ export default function TextField(props: TextFieldProps) {
|
||||
onBlur,
|
||||
onChange,
|
||||
onPressEnter,
|
||||
pattern,
|
||||
placeholder,
|
||||
required,
|
||||
status,
|
||||
tip,
|
||||
type,
|
||||
useTrim,
|
||||
value,
|
||||
} = props;
|
||||
|
||||
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
|
||||
const handleChange = (e: any) => {
|
||||
const val = type === TEXTFIELD_TYPE_NUMBER ? e : e.target.value;
|
||||
// if an extra onChange handler was sent in as a prop, let's run that too.
|
||||
if (onChange) {
|
||||
onChange({ fieldName, value: val });
|
||||
const val = type === TEXTFIELD_TYPE_NUMBER ? e : e.target.value;
|
||||
onChange({ fieldName, value: useTrim ? val.trim() : val });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,6 +103,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
} else if (type === TEXTFIELD_TYPE_URL) {
|
||||
fieldProps = {
|
||||
type: 'url',
|
||||
pattern,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,6 +118,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
required,
|
||||
[`status-${statusType}`]: status,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
{label ? (
|
||||
@@ -130,7 +135,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
id={fieldId}
|
||||
className={`field ${className} ${fieldId}`}
|
||||
{...fieldProps}
|
||||
allowClear
|
||||
{...(type !== TEXTFIELD_TYPE_NUMBER && { allowClear: true })}
|
||||
placeholder={placeholder}
|
||||
maxLength={maxLength}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function CodecSelector() {
|
||||
<Title level={3} className="section-title">
|
||||
Video Codec
|
||||
</Title>
|
||||
<p className="description">
|
||||
<div className="description">
|
||||
If you have access to specific hardware with the drivers and software installed for them,
|
||||
you may be able to improve your video encoding performance.
|
||||
<p>
|
||||
@@ -133,7 +133,7 @@ export default function CodecSelector() {
|
||||
unplayable.
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
<div className="segment-slider-container">
|
||||
<Popconfirm
|
||||
title={`Are you sure you want to change your video codec to ${pendingSaveCodec} and understand what this means?`}
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function VideoVariantForm({
|
||||
<p className="selected-value-note">{cpuUsageNote()}</p>
|
||||
</div>
|
||||
<p className="read-more-subtext">
|
||||
This could mean GPU or CPU usage depending on your server environment. {' '}
|
||||
This could mean GPU or CPU usage depending on your server environment.{' '}
|
||||
<a
|
||||
href="https://owncast.online/docs/video/?source=admin#cpu-usage"
|
||||
target="_blank"
|
||||
|
||||
Reference in New Issue
Block a user