edit tags section

This commit is contained in:
gingervitis 2021-01-01 18:23:23 -08:00 committed by Gabe Kangas
parent 624ab72eb3
commit c93aefa05d
4 changed files with 191 additions and 35 deletions

View file

@ -1,4 +1,6 @@
// DEFAULT VALUES
import React from 'react';
import { CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons';
export const DEFAULT_NAME = 'Owncast User';
export const DEFAULT_TITLE = 'Owncast Server';
@ -6,8 +8,22 @@ export const DEFAULT_SUMMARY = '';
export const TEXT_MAXLENGTH = 255;
export const RESET_TIMEOUT = 3000;
export const SUCCESS_STATES = {
success: {
icon: <CheckCircleFilled style={{ color: 'green' }} />,
message: 'Success!',
},
error: {
icon: <ExclamationCircleFilled style={{ color: 'red' }} />,
message: 'An error occurred.',
},
};
// Creating this so that it'll be easier to change values in one place, rather than looking for places to change it in a sea of JSX.
// key is the input's `fieldName`
export const TEXTFIELD_DEFAULTS = {
@ -95,5 +111,16 @@ export const TEXTFIELD_DEFAULTS = {
label: 'Server port',
tip: 'What port are you serving Owncast from? Default is :8080',
},
//
tags: {
apiPath: '/tags',
defaultValue: '',
maxLength: 24,
placeholder: 'Add a new tag',
configPath: 'instanceDetails',
label: '',
tip: '',
}
}

View file

@ -22,7 +22,7 @@ import { FormItemProps } from 'antd/es/form';
import { InfoCircleOutlined } from '@ant-design/icons';
import { TEXTFIELD_DEFAULTS, TEXT_MAXLENGTH } from './defaults';
import { TEXTFIELD_DEFAULTS, TEXT_MAXLENGTH, RESET_TIMEOUT } from './constants';
import { TextFieldProps } from '../../../types/config-section';
import { fetchData, SERVER_CONFIG_UPDATE_URL } from '../../../utils/apis';
@ -84,7 +84,7 @@ export default function TextField(props: TextFieldProps) {
setSubmitStatus('error');
setSubmitStatusMessage(`There was an error: ${result.message}`);
}
resetTimer = setTimeout(resetStates, 3000);
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
};
const handleChange = e => {
@ -139,7 +139,7 @@ export default function TextField(props: TextFieldProps) {
<Form.Item
label={label}
name={fieldName}
// hasFeedback
hasFeedback
validateStatus={submitStatus}
help={submitStatusMessage}
>

View file

@ -1,33 +1,105 @@
/* eslint-disable react/no-array-index-key */
import React, { useContext, useEffect } from 'react';
import { Typography, Button, Tooltip } from 'antd';
import { CloseCircleOutlined } from '@ant-design/icons';
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Tag, Input } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { fetchData, SERVER_CONFIG_UPDATE_URL } from '../../../utils/apis';
import { TEXTFIELD_DEFAULTS, RESET_TIMEOUT, SUCCESS_STATES } from './constants';
const { Title } = Typography;
function Tag({ label }) {
return (
<Button className="tag" type="text" shape="round">
{label}
<Tooltip title="Delete this tag.">
<Button type="link" size="small" className="tag-delete">
<CloseCircleOutlined />
</Button>
</Tooltip>
</Button>
);
}
export default function EditInstanceTags() {
const [newTagInput, setNewTagInput] = useState('');
const [submitStatus, setSubmitStatus] = useState(null);
const [submitDetails, setSubmitDetails] = useState('');
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
const { serverConfig, setConfigField } = serverStatusData || {};
const { instanceDetails } = serverConfig;
const { tags = [] } = instanceDetails;
console.log(tags)
const {
apiPath,
maxLength,
placeholder,
configPath,
} = TEXTFIELD_DEFAULTS.tags || {};
let resetTimer = null;
useEffect(() => {
return () => {
clearTimeout(resetTimer);
}
}, []);
const resetStates = () => {
setSubmitStatus(null);
setSubmitDetails('');
resetTimer = null;
clearTimeout(resetTimer);
}
// posts all the tags at once as an array obj
const postUpdateToAPI = async (postValue: any) => {
// const result = await fetchData(`${SERVER_CONFIG_UPDATE_URL}${apiPath}`, {
// data: { value: postValue },
// method: 'POST',
// auth: true,
// });
const result = {
success: true,
message: 'success yay'
}
if (result.success) {
setConfigField({ fieldName: 'tags', value: postValue, path: configPath });
setSubmitStatus('success');
setSubmitDetails('Tags updated.');
setNewTagInput('');
} else {
setSubmitStatus('error');
setSubmitDetails(result.message);
}
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
};
const handleInputChange = e => {
if (submitDetails !== '') {
setSubmitDetails('');
}
setNewTagInput(e.target.value);
};
// send to api and do stuff
const handleSubmitNewTag = () => {
resetStates();
const newTag = newTagInput.trim();
if (newTag === '') {
setSubmitDetails('Please enter a tag');
return;
}
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) {
setSubmitDetails('This tag is already used!');
return;
}
const updatedTags = [...tags, newTag];
postUpdateToAPI(updatedTags);
};
const handleDeleteTag = index => {
resetStates();
const updatedTags = [...tags];
updatedTags.splice(index, 1);
postUpdateToAPI(updatedTags);
}
const {
icon: newStatusIcon = null,
message: newStatusMessage = '',
} = SUCCESS_STATES[submitStatus] || {};
return (
<div className="tag-editor-container">
@ -35,9 +107,31 @@ export default function EditInstanceTags() {
<p>This is a great way to categorize your Owncast server on the Directory!</p>
<div className="tag-current-tags">
{tags.map((tag, index) => <Tag label={tag} key={`tag-${tag}-${index}`} />)}
{tags.map((tag, index) => {
const handleClose = () => {
handleDeleteTag(index);
};
return (
<Tag closable onClose={handleClose} key={`tag-${tag}-${index}`}>{tag}</Tag>
);
})}
</div>
<div className={`add-new-status ${submitStatus || ''}`}>
{newStatusIcon} {newStatusMessage} {submitDetails}
</div>
<div className="add-new-tag-section">
<Input
type="text"
className="new-tag-input"
value={newTagInput}
onChange={handleInputChange}
onPressEnter={handleSubmitNewTag}
maxLength={maxLength}
placeholder={placeholder}
allowClear
/>
</div>
</div>
);
}

View file

@ -66,16 +66,51 @@
right: 0;
bottom: .5em;
}
.tag {
background-color: white;
border-color: gray;
.tag-delete {
padding: 0 0 0 .3rem;
margin-top: -4px;
.tag-current-tags {
.ant-tag {
margin: .1rem;
font-size: .85rem;
border-radius: 10em;
padding: .25em 1em;
background-color: rgba(255,255,255,.5);
.ant-tag-close-icon {
transform: translateY(-1px);
margin-left: .3rem;
padding: 2px;
border-radius: 5rem;
border: 1px solid #eee;
&:hover {
border-color: #e03;
svg {
fill: black;
transition: fill .3s;
}
}
}
}
}
.tag-current-tags {
.tag {
margin: .25rem ;
.add-new-tag-section {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.new-tag-input {
width: 16em;
}
}
}
.add-new-status {
margin: 1em 0;
min-height: 1.25em;
font-size: .75rem;
&.success {
color: var(--ant-success);
}
&.error {
color: var(--ant-error);
}
}