add comment support button

This commit is contained in:
Sean Yesmunt 2018-11-07 17:44:38 -05:00
parent f621440b3f
commit 863b2e32ce
16 changed files with 261 additions and 71 deletions

View file

@ -0,0 +1,7 @@
import { connect } from 'react-redux';
import Expandable from './view';
export default connect(
null,
null
)(Expandable);

View file

@ -0,0 +1,57 @@
// @flow
import React, { PureComponent, Node } from 'react';
import classnames from 'classnames';
import Button from 'component/button';
// Note:
// When we use this in other parts of the app, we will probably need to
// add props for collapsed height
type Props = {
children: Node | Array<Node>,
};
type State = {
expanded: boolean,
};
export default class Expandable extends PureComponent<Props, State> {
constructor() {
super();
this.state = {
expanded: false,
};
(this: any).handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
expanded: !this.state.expanded,
});
}
render() {
const { children } = this.props;
const { expanded } = this.state;
return (
<div className="expandable">
<div
className={classnames({
'expandable--open': expanded,
'expandable--closed': !expanded,
})}
>
{children}
</div>
<Button
button="link"
label={expanded ? __('Less') : __('More')}
onClick={this.handleClick}
/>
</div>
);
}
}

View file

@ -4,8 +4,12 @@ import {
makeSelectContentTypeForUri, makeSelectContentTypeForUri,
makeSelectMetadataForUri, makeSelectMetadataForUri,
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
doNotify,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectUser } from 'lbryinc';
import { doOpenFileInFolder } from 'redux/actions/file'; import { doOpenFileInFolder } from 'redux/actions/file';
import { selectHasClickedComment } from 'redux/selectors/app';
import { doClickCommentButton } from 'redux/actions/app';
import FileDetails from './view'; import FileDetails from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -13,10 +17,14 @@ const select = (state, props) => ({
contentType: makeSelectContentTypeForUri(props.uri)(state), contentType: makeSelectContentTypeForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
hasClickedComment: selectHasClickedComment(state),
user: selectUser(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
openFolder: path => dispatch(doOpenFileInFolder(path)), openFolder: path => dispatch(doOpenFileInFolder(path)),
showSnackBar: message => dispatch(doNotify({ message, displayType: ['snackbar'] })),
clickCommentButton: () => dispatch(doClickCommentButton()),
}); });
export default connect( export default connect(

View file

@ -1,77 +1,128 @@
// @flow // @flow
import * as React from 'react'; import type { Claim, Metadata } from 'types/claim';
import type { FileInfo } from 'types/file_info';
import React, { Fragment, PureComponent } from 'react';
import { Lbryio } from 'lbryinc';
import MarkdownPreview from 'component/common/markdown-preview'; import MarkdownPreview from 'component/common/markdown-preview';
import Button from 'component/button'; import Button from 'component/button';
import path from 'path'; import path from 'path';
import type { Claim } from 'types/claim'; import Expandable from 'component/expandable';
type Props = { type Props = {
claim: Claim, claim: Claim,
fileInfo: { fileInfo: FileInfo,
download_path: string, metadata: Metadata,
},
metadata: {
description: string,
language: string,
license: string,
},
openFolder: string => void, openFolder: string => void,
contentType: string, contentType: string,
clickCommentButton: () => void,
showSnackBar: string => void,
hasClickedComment: boolean,
user: ?any,
}; };
const FileDetails = (props: Props) => { class FileDetails extends PureComponent<Props> {
const { claim, contentType, fileInfo, metadata, openFolder } = props; constructor() {
super();
if (!claim || !metadata) { (this: any).handleCommentClick = this.handleCommentClick.bind(this);
return (
<div className="card__content">
<span className="empty">{__('Empty claim or metadata info.')}</span>
</div>
);
} }
const { description, language, license } = metadata; handleCommentClick() {
const { clickCommentButton, showSnackBar } = this.props;
const mediaType = contentType || 'unknown'; clickCommentButton();
const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null; Lbryio.call('user_tag', 'edit', { add: 'comments-waitlist' });
showSnackBar(__('Thanks! This feature is so beta, all we had time for was this button.'));
}
return ( render() {
<React.Fragment> const {
{description && ( claim,
<React.Fragment> contentType,
<div className="card__subtext-title">About</div> fileInfo,
metadata,
openFolder,
hasClickedComment,
user,
} = this.props;
if (!claim || !metadata) {
return (
<div className="card__content">
<span className="empty">{__('Empty claim or metadata info.')}</span>
</div>
);
}
const { description, language, license } = metadata;
const mediaType = contentType || 'unknown';
const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null;
return (
<Fragment>
<Expandable>
{description && (
<Fragment>
<div className="card__subtext-title">About</div>
<div className="card__subtext">
<MarkdownPreview content={description} promptLinks />
</div>
</Fragment>
)}
<div className="card__subtext-title">Info</div>
<div className="card__subtext"> <div className="card__subtext">
<MarkdownPreview content={description} promptLinks={true} /> <div>
{__('Content-Type')}
{': '}
{mediaType}
</div>
<div>
{__('Language')}
{': '}
{language}
</div>
<div>
{__('License')}
{': '}
{license}
</div>
{downloadPath && (
<div>
{__('Downloaded to')}
{': '}
<Button
button="link"
onClick={() => openFolder(downloadPath)}
label={downloadPath}
/>
</div>
)}
</div> </div>
</React.Fragment> </Expandable>
)} <div className="card__content">
<div className="card__subtext-title">Info</div> <div className="card__subtext-title">Comments</div>
<div className="card__subtext"> <div className="card__actions card__actions--center">
<div> <Button
{__('Content-Type')} data-id="add-comment"
{': '} disabled={hasClickedComment}
{mediaType} button="primary"
</div> label="Comment"
<div> onClick={this.handleCommentClick}
{__('Language')} />
{': '}
{language}
</div>
<div>
{__('License')}
{': '}
{license}
</div>
{downloadPath && (
<div>
{__('Downloaded to')}
{': '}
<Button button="link" onClick={() => openFolder(downloadPath)} label={downloadPath} />
</div> </div>
)} {hasClickedComment && (
</div> <p className="main--for-content">
</React.Fragment> {user
); ? __(
}; 'Your support has been added. You will be notified when comments are available.'
)
: __('Your support has been added. Comments are coming soon.')}
</p>
)}
</div>
</Fragment>
);
}
}
export default FileDetails; export default FileDetails;

View file

@ -13,6 +13,7 @@ export const DAEMON_READY = 'DAEMON_READY';
export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH'; export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH';
export const DAEMON_VERSION_MISMATCH = 'DAEMON_VERSION_MISMATCH'; export const DAEMON_VERSION_MISMATCH = 'DAEMON_VERSION_MISMATCH';
export const VOLUME_CHANGED = 'VOLUME_CHANGED'; export const VOLUME_CHANGED = 'VOLUME_CHANGED';
export const ADD_COMMENT = 'ADD_COMMENT';
// Navigation // Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH'; export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
@ -36,6 +37,7 @@ export const SKIP_UPGRADE = 'SKIP_UPGRADE';
export const START_UPGRADE = 'START_UPGRADE'; export const START_UPGRADE = 'START_UPGRADE';
export const AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED'; export const AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED';
export const AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED'; export const AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED';
export const CLEAR_UPGRADE_TIMER = 'CLEAR_UPGRADE_TIMER';
// Wallet // Wallet
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED'; export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED';

View file

@ -123,11 +123,16 @@ document.addEventListener('drop', event => {
}); });
document.addEventListener('click', event => { document.addEventListener('click', event => {
let { target } = event; let { target } = event;
while (target && target !== document) { while (target && target !== document) {
if (target.matches('a') || target.matches('button')) { if (target.matches('a') || target.matches('button')) {
// TODO: Look into using accessiblity labels (this would also make the app more accessible) // TODO: Look into using accessiblity labels (this would also make the app more accessible)
const hrefParts = window.location.href.split('#'); const hrefParts = window.location.href.split('#');
const element = target.title || (target.textContent && target.textContent.trim());
// Buttons that we want to track should use `data-id`
// This prevents multiple buttons being grouped together if they have the same text
const element =
target.dataset.id || target.title || (target.textContent && target.textContent.trim());
if (element) { if (element) {
analytics.track('CLICK', { analytics.track('CLICK', {
target: element, target: element,

View file

@ -2,8 +2,8 @@ import { execSync } from 'child_process';
import isDev from 'electron-is-dev'; import isDev from 'electron-is-dev';
import path from 'path'; import path from 'path';
import { ipcRenderer, remote } from 'electron'; import { ipcRenderer, remote } from 'electron';
import * as ACTIONS from 'constants/action_types';
import { import {
ACTIONS,
Lbry, Lbry,
doBalanceSubscribe, doBalanceSubscribe,
doFetchFileInfosAndPublishedClaims, doFetchFileInfosAndPublishedClaims,
@ -387,6 +387,12 @@ export function doChangeVolume(volume) {
}; };
} }
export function doClickCommentButton() {
return {
type: ACTIONS.ADD_COMMENT,
};
}
export function doConditionalAuthNavigate(newSession) { export function doConditionalAuthNavigate(newSession) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();

View file

@ -33,7 +33,7 @@ export type AppState = {
checkUpgradeTimer: ?number, checkUpgradeTimer: ?number,
isUpgradeAvailable: ?boolean, isUpgradeAvailable: ?boolean,
isUpgradeSkipped: ?boolean, isUpgradeSkipped: ?boolean,
snackBar: ?SnackBar, hasClickedComment: boolean,
}; };
const defaultState: AppState = { const defaultState: AppState = {
@ -50,14 +50,13 @@ const defaultState: AppState = {
autoUpdateDownloaded: false, autoUpdateDownloaded: false,
autoUpdateDeclined: false, autoUpdateDeclined: false,
modalsAllowed: true, modalsAllowed: true,
hasClickedComment: false,
downloadProgress: undefined, downloadProgress: undefined,
upgradeDownloading: undefined, upgradeDownloading: undefined,
upgradeDownloadComplete: undefined, upgradeDownloadComplete: undefined,
checkUpgradeTimer: undefined, checkUpgradeTimer: undefined,
isUpgradeAvailable: undefined, isUpgradeAvailable: undefined,
isUpgradeSkipped: undefined, isUpgradeSkipped: undefined,
snackBar: undefined,
}; };
reducers[ACTIONS.DAEMON_READY] = state => reducers[ACTIONS.DAEMON_READY] = state =>
@ -189,6 +188,11 @@ reducers[ACTIONS.CLEAR_UPGRADE_TIMER] = state =>
checkUpgradeTimer: undefined, checkUpgradeTimer: undefined,
}); });
reducers[ACTIONS.ADD_COMMENT] = state =>
Object.assign({}, state, {
hasClickedComment: true,
});
export default function reducer(state: AppState = defaultState, action: any) { export default function reducer(state: AppState = defaultState, action: any) {
const handler = reducers[action.type]; const handler = reducers[action.type];
if (handler) return handler(state, action); if (handler) return handler(state, action);

View file

@ -19,6 +19,11 @@ export const selectUpdateUrl = createSelector(selectPlatform, platform => {
} }
}); });
export const selectHasClickedComment = createSelector(
selectState,
state => state.hasClickedComment
);
export const selectRemoteVersion = createSelector(selectState, state => state.remoteVersion); export const selectRemoteVersion = createSelector(selectState, state => state.remoteVersion);
export const selectIsUpgradeAvailable = createSelector( export const selectIsUpgradeAvailable = createSelector(

View file

@ -130,7 +130,7 @@ p:not(:first-of-type) {
bottom: 0; bottom: 0;
right: 0; right: 0;
background-color: rgba($lbry-gray-1, 0.3); background-color: mix($lbry-white, $lbry-gray-1, 70%);
display: flex; display: flex;
position: absolute; position: absolute;
z-index: 0; z-index: 0;
@ -245,11 +245,6 @@ p:not(:first-of-type) {
padding: 0; padding: 0;
} }
.divider__horizontal {
border-top: $lbry-gray-2;
margin: 16px 0;
}
.hidden { .hidden {
display: none; display: none;
} }

View file

@ -6,4 +6,4 @@
'component/markdown-editor', 'component/scrollbar', 'component/spinner', 'component/nav', 'component/markdown-editor', 'component/scrollbar', 'component/spinner', 'component/nav',
'component/file-list', 'component/file-render', 'component/search', 'component/toggle', 'component/file-list', 'component/file-render', 'component/search', 'component/toggle',
'component/dat-gui', 'component/item-list', 'component/time', 'component/icon', 'component/dat-gui', 'component/item-list', 'component/time', 'component/icon',
'component/placeholder', 'component/badge', 'themes/dark'; 'component/placeholder', 'component/badge', 'component/expandable', 'themes/dark';

View file

@ -0,0 +1,28 @@
.expandable {
border-bottom: var(--input-border-size) solid $lbry-gray-3;
padding-bottom: $spacing-vertical * 1/3;
}
.expandable--open {
max-height: 100%;
}
.expandable--closed {
max-height: 10em;
position: relative;
overflow: hidden;
}
.expandable--closed::after {
content: '';
width: 100%;
height: 20%;
position: absolute;
left: 0;
bottom: 0;
background-image: linear-gradient(
to bottom,
transparent 0%,
mix($lbry-white, $lbry-gray-1, 70%) 90%
);
}

View file

@ -83,7 +83,7 @@ html[data-theme='dark'] {
} }
// //
// BUTTON // Button
// //
.btn { .btn {
&.btn--alt:not(:disabled) { &.btn--alt:not(:disabled) {
@ -178,4 +178,15 @@ html[data-theme='dark'] {
} }
} }
} }
//
// Expandable
//
.expandable {
border-bottom: var(--input-border-size) solid $lbry-gray-5;
}
.expandable--closed::after {
background-image: linear-gradient(to bottom, transparent 0%, $lbry-black 90%);
}
} }

View file

@ -108,6 +108,7 @@ const fileInfoFilter = createFilter('fileInfo', [
'fileListDownloadedSort', 'fileListDownloadedSort',
'fileListSubscriptionSort', 'fileListSubscriptionSort',
]); ]);
const appFilter = createFilter('app', ['hasClickedComment']);
// We only need to persist the receiveAddress for the wallet // We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']); const walletFilter = createFilter('wallet', ['receiveAddress']);
@ -115,7 +116,14 @@ const persistOptions = {
whitelist: ['subscriptions', 'publish', 'wallet', 'content', 'fileInfo'], whitelist: ['subscriptions', 'publish', 'wallet', 'content', 'fileInfo'],
// Order is important. Needs to be compressed last or other transforms can't // Order is important. Needs to be compressed last or other transforms can't
// read the data // read the data
transforms: [subscriptionsFilter, walletFilter, contentFilter, fileInfoFilter, compressor], transforms: [
subscriptionsFilter,
walletFilter,
contentFilter,
fileInfoFilter,
appFilter,
compressor,
],
debounce: 10000, debounce: 10000,
storage: localForage, storage: localForage,
}; };

View file

@ -14,6 +14,8 @@ export type Metadata = {
title: string, title: string,
thumbnail: ?string, thumbnail: ?string,
description: ?string, description: ?string,
license: ?string,
language: string,
fee?: fee?:
| { | {
amount: number, // should be a string https://github.com/lbryio/lbry/issues/1576 amount: number, // should be a string https://github.com/lbryio/lbry/issues/1576

View file

@ -7,6 +7,7 @@ export type FileInfo = {
pending?: boolean, pending?: boolean,
channel_claim_id: string, channel_claim_id: string,
file_name: string, file_name: string,
download_path: string,
value?: { value?: {
publisherSignature: { publisherSignature: {
certificateId: string, certificateId: string,