new custom comment servers ux
This commit is contained in:
parent
f370aa8db1
commit
d13397d4dd
12 changed files with 296 additions and 50 deletions
8
flow-typed/Settings.js
vendored
Normal file
8
flow-typed/Settings.js
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
declare type CommentServerDetails = {
|
||||
name: string,
|
||||
url: string,
|
||||
}
|
||||
|
||||
declare type WalletServerDetails = {
|
||||
|
||||
};
|
|
@ -2269,5 +2269,11 @@
|
|||
"Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.": "Automatic Hosting lets you delegate some amount of storage for the network to automatically download and host.",
|
||||
"Help improve the P2P data network (and make LBRY happy) by hosting data.": "Help improve the P2P data network (and make LBRY happy) by hosting data.",
|
||||
"Limit Hosting of Content History": "Limit Hosting of Content History",
|
||||
"Remove custom comment server": "Remove custom comment server",
|
||||
"Use Https": "Use Https",
|
||||
"Server URL": "Server URL",
|
||||
"Use https": "Use https",
|
||||
"Custom Servers": "Custom Servers",
|
||||
"Add A Server": "Add A Server",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
104
ui/component/common/item-panel-input-row.jsx
Normal file
104
ui/component/common/item-panel-input-row.jsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
// @flow
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Button from 'component/button';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
|
||||
type Props = {
|
||||
update: (CommentServerDetails) => void,
|
||||
onCancel: (boolean) => void,
|
||||
};
|
||||
|
||||
const VALID_IPADDRESS_REGEX = new RegExp(
|
||||
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\.)){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
|
||||
);
|
||||
const VALID_HOSTNAME_REGEX = new RegExp(
|
||||
'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(\\.))+([A-Za-z]|[A-Za-z][A-Za-z]*[A-Za-z])$'
|
||||
);
|
||||
|
||||
const VALID_ENDPOINT_REGEX = new RegExp('^((\\/)([a-zA-Z0-9]+))+$');
|
||||
|
||||
const isValidServerString = (serverString) => {
|
||||
const si = serverString.indexOf('/');
|
||||
const pi = serverString.indexOf(':');
|
||||
const path = si === -1 ? '' : serverString.slice(si);
|
||||
console.log('path', path);
|
||||
const hostMaybePort = si === -1 ? serverString : serverString.slice(0, si);
|
||||
const host = pi === -1 ? hostMaybePort : hostMaybePort.slice(0, pi);
|
||||
const port = pi === -1 ? '' : hostMaybePort.slice(pi + 1);
|
||||
console.log('port', port);
|
||||
const portInt = parseInt(port);
|
||||
|
||||
return (
|
||||
(host === 'localhost' || VALID_IPADDRESS_REGEX.test(host) || VALID_HOSTNAME_REGEX.test(host)) &&
|
||||
(!path || VALID_ENDPOINT_REGEX.test(path)) &&
|
||||
// eslint-disable-next-line
|
||||
(pi === -1 || (port && typeof portInt === 'number' && portInt === portInt))
|
||||
); // NaN !== NaN
|
||||
};
|
||||
|
||||
function ServerInputRow(props: Props) {
|
||||
const { update, onCancel } = props;
|
||||
const [nameString, setNameString] = useState('');
|
||||
const [hostString, setHostString] = useState('');
|
||||
const [useHttps, setUseHttps] = useState(true);
|
||||
|
||||
const getHostString = () => {
|
||||
return `${useHttps ? 'https://' : 'http://'}${hostString}`;
|
||||
};
|
||||
|
||||
const [validServerString, setValidServerString] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setValidServerString(isValidServerString(hostString));
|
||||
}, [hostString, validServerString, setValidServerString]);
|
||||
|
||||
function onSubmit() {
|
||||
const updateValue = { url: getHostString(), name: nameString };
|
||||
update(updateValue);
|
||||
setHostString('');
|
||||
setNameString('');
|
||||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={onSubmit}>
|
||||
<div className="itemPanel--input">
|
||||
<FormField
|
||||
type="text"
|
||||
label={__('Name')}
|
||||
placeholder={'My Server'}
|
||||
value={nameString}
|
||||
onChange={(e) => setNameString(e.target.value)}
|
||||
/>
|
||||
<div className="fieldset-group fieldset-group--smushed fieldset-group--disabled-prefix fieldset-group--row">
|
||||
<div className={'fieldset-section'}>
|
||||
<label htmlFor="serverUrl">{__('URL')}</label>
|
||||
<div className="form-field__prefix">{`${useHttps ? 'https://' : 'http://'}`}</div>
|
||||
</div>
|
||||
<FormField
|
||||
type="text"
|
||||
placeholder={'code.freezepeach.fun'}
|
||||
value={hostString}
|
||||
onChange={(e) => setHostString(e.target.value)}
|
||||
name={'serverUrl'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="itemPanel--input">
|
||||
<FormField
|
||||
label={'Use Https'}
|
||||
name="use_https"
|
||||
type="checkbox"
|
||||
checked={useHttps}
|
||||
onChange={() => setUseHttps(!useHttps)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="section__actions">
|
||||
<Button type="submit" button="primary" label={__('Add')} disabled={!validServerString || !nameString} />
|
||||
<Button type="button" button="link" onClick={() => onCancel(false)} label={__('Cancel')} />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServerInputRow;
|
43
ui/component/common/item-panel.jsx
Normal file
43
ui/component/common/item-panel.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
onClick: (CommentServerDetails) => void,
|
||||
onRemove?: (CommentServerDetails) => void,
|
||||
active: boolean,
|
||||
serverDetails: CommentServerDetails,
|
||||
};
|
||||
|
||||
/*
|
||||
[ https://myserver.com x ]
|
||||
[ https://myserver.com x (selected)]
|
||||
|
||||
[ https://myserver.com:50001 x (selected)]
|
||||
*/
|
||||
|
||||
const ItemPanel = (props: Props) => {
|
||||
const { onClick, active, serverDetails, onRemove } = props;
|
||||
|
||||
return (
|
||||
<div onClick={() => onClick(serverDetails)} className={classnames('itemPanel', { 'itemPanel--active': active })}>
|
||||
<div className={'itemPanel__details'}>
|
||||
<div className={'itemPanel__name'}>{`${serverDetails.name}`}</div>
|
||||
<div className={'itemPanel__url'}>{`${serverDetails.url}`}</div>
|
||||
</div>
|
||||
{onRemove && (
|
||||
<Button
|
||||
button="close"
|
||||
title={__('Remove custom comment server')}
|
||||
icon={ICONS.REMOVE}
|
||||
onClick={() => onRemove(serverDetails)}
|
||||
/>
|
||||
)}
|
||||
{!onRemove && <div />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemPanel;
|
|
@ -7,11 +7,13 @@ import SettingCommentsServer from './view';
|
|||
const select = (state) => ({
|
||||
customServerEnabled: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED)(state),
|
||||
customServerUrl: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_URL)(state),
|
||||
customCommentServers: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVERS)(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
setCustomServerEnabled: (val) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED, val, true)),
|
||||
setCustomServerUrl: (url) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_URL, url, true)),
|
||||
setCustomServers: (servers) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVERS, servers, true)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SettingCommentsServer);
|
||||
|
|
|
@ -1,70 +1,101 @@
|
|||
// @flow
|
||||
import { COMMENT_SERVER_NAME } from 'config';
|
||||
import { COMMENT_SERVER_API } from 'config'; // COMMENT_SERVER_NAME,
|
||||
import React from 'react';
|
||||
import Comments from 'comments';
|
||||
import { FormField } from 'component/common/form';
|
||||
|
||||
const DEBOUNCE_TEXT_INPUT_MS = 500;
|
||||
import ItemPanel from 'component/common/item-panel';
|
||||
import ItemInputRow from 'component/common/item-panel-input-row';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
customServerEnabled: boolean,
|
||||
customServerUrl: string,
|
||||
setCustomServerEnabled: (boolean) => void,
|
||||
setCustomServerUrl: (string) => void,
|
||||
setCustomServers: (Array<CommentServerDetails>) => void,
|
||||
customCommentServers: Array<CommentServerDetails>,
|
||||
};
|
||||
|
||||
const defaultServer = { name: 'Default', url: COMMENT_SERVER_API };
|
||||
function SettingCommentsServer(props: Props) {
|
||||
const { customServerEnabled, customServerUrl, setCustomServerEnabled, setCustomServerUrl } = props;
|
||||
const [url, setUrl] = React.useState(customServerUrl);
|
||||
const {
|
||||
customServerEnabled,
|
||||
customServerUrl,
|
||||
setCustomServerEnabled,
|
||||
setCustomServerUrl,
|
||||
customCommentServers,
|
||||
setCustomServers,
|
||||
} = props;
|
||||
const [addServer, setAddServer] = React.useState(false);
|
||||
|
||||
const customServersString = JSON.stringify(customCommentServers);
|
||||
|
||||
// "migrate" to make sure any currently set custom server is in saved list
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
Comments.setServerUrl(customServerEnabled ? url : undefined);
|
||||
if (url !== customServerUrl) {
|
||||
setCustomServerUrl(url);
|
||||
}
|
||||
}, DEBOUNCE_TEXT_INPUT_MS);
|
||||
// const servers = JSON.parse(customServersString);
|
||||
// if customServerUrl is not in servers, make sure it is.
|
||||
}, [customServerUrl, customServersString, setCustomServers]);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [url, customServerUrl, customServerEnabled, setCustomServerUrl]);
|
||||
// React.useEffect(() => {
|
||||
// const timer = setTimeout(() => {
|
||||
// Comments.setServerUrl(customServerEnabled ? url : undefined);
|
||||
// if (url !== customServerUrl) {
|
||||
// setCustomServerUrl(url);
|
||||
// }
|
||||
// }, DEBOUNCE_TEXT_INPUT_MS);
|
||||
//
|
||||
// return () => clearTimeout(timer);
|
||||
// }, [url, customServerUrl, customServerEnabled, setCustomServerUrl]);
|
||||
|
||||
const handleSelectServer = (serverItem: CommentServerDetails) => {
|
||||
if (serverItem.url !== COMMENT_SERVER_API) {
|
||||
alert(`set ${serverItem.url}`);
|
||||
Comments.setServerUrl(serverItem.url);
|
||||
setCustomServerUrl(serverItem.url);
|
||||
setCustomServerEnabled(true);
|
||||
} else {
|
||||
alert('reset');
|
||||
Comments.setServerUrl(undefined);
|
||||
setCustomServerEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddServer = (serverItem: CommentServerDetails) => {
|
||||
const newCustomServers = customCommentServers.slice();
|
||||
newCustomServers.push(serverItem);
|
||||
setCustomServers(newCustomServers);
|
||||
handleSelectServer(serverItem);
|
||||
};
|
||||
|
||||
const handleRemoveServer = (serverItem) => {
|
||||
handleSelectServer(defaultServer);
|
||||
const newCustomServers = customCommentServers.slice().filter((server) => {
|
||||
return server.url !== serverItem.url;
|
||||
});
|
||||
setCustomServers(newCustomServers);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<fieldset-section>
|
||||
<FormField
|
||||
type="radio"
|
||||
name="use_default_comments_server"
|
||||
label={__('Default comments server (%name%)', { name: COMMENT_SERVER_NAME })}
|
||||
checked={!customServerEnabled}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setCustomServerEnabled(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
type="radio"
|
||||
name="use_custom_comments_server"
|
||||
label={__('Custom comments server')}
|
||||
checked={customServerEnabled}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setCustomServerEnabled(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{customServerEnabled && (
|
||||
<div className="section__body">
|
||||
<FormField
|
||||
type="text"
|
||||
placeholder="https://comment.mysite.com"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<div className={'fieldset-section'}>
|
||||
<ItemPanel onClick={handleSelectServer} active={!customServerEnabled} serverDetails={defaultServer} />
|
||||
{!!customCommentServers.length && <label>{__('Custom Servers')}</label>}
|
||||
{customCommentServers.map((server) => (
|
||||
<ItemPanel
|
||||
key={server.url}
|
||||
active={customServerEnabled && customServerUrl === server.url}
|
||||
onClick={handleSelectServer}
|
||||
serverDetails={server}
|
||||
onRemove={handleRemoveServer}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className={'fieldset-section'}>
|
||||
{!addServer && (
|
||||
<div className="section__actions">
|
||||
<Button type="button" button="link" onClick={() => setAddServer(true)} label={__('Add A Server')} />
|
||||
</div>
|
||||
)}
|
||||
</fieldset-section>
|
||||
{addServer && <ItemInputRow update={handleAddServer} onCancel={setAddServer} />}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ export const VIDEO_THEATER_MODE = 'video_theater_mode';
|
|||
export const VIDEO_PLAYBACK_RATE = 'video_playback_rate';
|
||||
export const CUSTOM_COMMENTS_SERVER_ENABLED = 'custom_comments_server_enabled';
|
||||
export const CUSTOM_COMMENTS_SERVER_URL = 'custom_comments_server_url';
|
||||
export const CUSTOM_COMMENTS_SERVERS = 'custom_comments_servers';
|
||||
export const CUSTOM_SHARE_URL_ENABLED = 'custom_share_url_enabled';
|
||||
export const CUSTOM_SHARE_URL = 'custom_share_url';
|
||||
export const ENABLE_PRERELEASE_UPDATES = 'enable_prerelease_updates';
|
||||
|
|
|
@ -45,6 +45,7 @@ const defaultState = {
|
|||
[SETTINGS.DESKTOP_WINDOW_ZOOM]: 1,
|
||||
[SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED]: false,
|
||||
[SETTINGS.CUSTOM_COMMENTS_SERVER_URL]: '',
|
||||
[SETTINGS.CUSTOM_COMMENTS_SERVERS]: [],
|
||||
[SETTINGS.CUSTOM_SHARE_URL_ENABLED]: false,
|
||||
[SETTINGS.CUSTOM_SHARE_URL]: '',
|
||||
|
||||
|
|
|
@ -68,3 +68,4 @@
|
|||
@import 'component/wallet-tip-send';
|
||||
@import 'component/swipe-list';
|
||||
@import 'component/utils';
|
||||
@import 'component/item-panel';
|
||||
|
|
|
@ -293,7 +293,9 @@ input[type='number'] {
|
|||
|
||||
fieldset-group {
|
||||
+ fieldset-group {
|
||||
margin-top: var(--spacing-s);
|
||||
&:not(.fieldset-group--row) {
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
&.fieldset-group--smushed {
|
||||
|
@ -339,6 +341,9 @@ fieldset-group {
|
|||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:not(.fieldset-group--row) {
|
||||
}
|
||||
}
|
||||
|
||||
// This is a special case where the prefix appears "inside" the input
|
||||
|
|
42
ui/scss/component/_item-panel.scss
Normal file
42
ui/scss/component/_item-panel.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
.itemPanel {
|
||||
padding: var(--spacing-m);
|
||||
margin-bottom: var(--spacing-m);
|
||||
width: 100%;
|
||||
background-color: var(--color-card-background);
|
||||
border-radius: var(--card-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.button--close {
|
||||
position: unset;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.itemPanel__details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
@media (max-width: $breakpoint-small) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.itemPanel__name {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
.itemPanel__url {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.itemPanel--active {
|
||||
color: var(--color-button-toggle-text);
|
||||
background-color: var(--color-button-toggle-bg);
|
||||
}
|
||||
|
||||
.itemPanel--input {
|
||||
padding: 0 0 var(--spacing-s) 0;
|
||||
}
|
|
@ -250,7 +250,9 @@
|
|||
}
|
||||
|
||||
fieldset-group {
|
||||
margin-top: var(--spacing-m);
|
||||
&:not(.fieldset-group--row) {
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
.tags__input-wrapper {
|
||||
|
|
Loading…
Reference in a new issue