From c93aefa05d6e796dbb989cd9b990baf00ee33324 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Fri, 1 Jan 2021 18:23:23 -0800 Subject: [PATCH] edit tags section --- .../config/{defaults.ts => constants.tsx} | 27 ++++ .../components/config/form-textfield.tsx | 6 +- web/pages/components/config/tags.tsx | 138 +++++++++++++++--- web/styles/config.scss | 55 +++++-- 4 files changed, 191 insertions(+), 35 deletions(-) rename web/pages/components/config/{defaults.ts => constants.tsx} (81%) diff --git a/web/pages/components/config/defaults.ts b/web/pages/components/config/constants.tsx similarity index 81% rename from web/pages/components/config/defaults.ts rename to web/pages/components/config/constants.tsx index e468595f8..b1bc93070 100644 --- a/web/pages/components/config/defaults.ts +++ b/web/pages/components/config/constants.tsx @@ -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: , + message: 'Success!', + }, + error: { + icon: , + 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: '', + } } diff --git a/web/pages/components/config/form-textfield.tsx b/web/pages/components/config/form-textfield.tsx index 18b01fcec..66d017e94 100644 --- a/web/pages/components/config/form-textfield.tsx +++ b/web/pages/components/config/form-textfield.tsx @@ -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) { diff --git a/web/pages/components/config/tags.tsx b/web/pages/components/config/tags.tsx index 7c5230a3b..fe97be1cf 100644 --- a/web/pages/components/config/tags.tsx +++ b/web/pages/components/config/tags.tsx @@ -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 ( - - - - - ); -} - 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 (
@@ -35,9 +107,31 @@ export default function EditInstanceTags() {

This is a great way to categorize your Owncast server on the Directory!

- {tags.map((tag, index) => )} + {tags.map((tag, index) => { + const handleClose = () => { + handleDeleteTag(index); + }; + return ( + {tag} + ); + })} +
+
+ {newStatusIcon} {newStatusMessage} {submitDetails} +
+
+ +
); } - diff --git a/web/styles/config.scss b/web/styles/config.scss index a6d97be2d..d24b6761f 100644 --- a/web/styles/config.scss +++ b/web/styles/config.scss @@ -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; } -} \ No newline at end of file +} +.add-new-status { + margin: 1em 0; + min-height: 1.25em; + font-size: .75rem; + &.success { + color: var(--ant-success); + } + &.error { + color: var(--ant-error); + } +} +