From f065eec22636ec6f92c8f192dc13fa3341be6511 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Wed, 7 Nov 2018 17:28:38 -0500 Subject: [PATCH] add comment support button --- src/renderer/component/expandable/index.js | 4 + src/renderer/component/expandable/view.jsx | 52 ++++++++ src/renderer/component/fileDetails/index.js | 8 ++ src/renderer/component/fileDetails/view.jsx | 130 ++++++++++++------- src/renderer/constants/action_types.js | 2 + src/renderer/index.js | 7 +- src/renderer/redux/actions/app.js | 8 +- src/renderer/redux/reducers/app.js | 10 +- src/renderer/redux/selectors/app.js | 5 + src/renderer/scss/_gui.scss | 7 +- src/renderer/scss/all.scss | 2 +- src/renderer/scss/component/_expandable.scss | 28 ++++ src/renderer/scss/themes/_dark.scss | 13 +- src/renderer/store.js | 10 +- 14 files changed, 224 insertions(+), 62 deletions(-) create mode 100644 src/renderer/component/expandable/index.js create mode 100644 src/renderer/component/expandable/view.jsx create mode 100644 src/renderer/scss/component/_expandable.scss diff --git a/src/renderer/component/expandable/index.js b/src/renderer/component/expandable/index.js new file mode 100644 index 00000000000..b5c81f015ff --- /dev/null +++ b/src/renderer/component/expandable/index.js @@ -0,0 +1,4 @@ +import { connect } from 'react-redux'; +import Expandable from './view'; + +export default connect(null, null)(Expandable); diff --git a/src/renderer/component/expandable/view.jsx b/src/renderer/component/expandable/view.jsx new file mode 100644 index 00000000000..dc9f7d2b73f --- /dev/null +++ b/src/renderer/component/expandable/view.jsx @@ -0,0 +1,52 @@ +// @flow +import React, { PureComponent } 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: React.Node | Array, +} + +type State = { + expanded: boolean, +} + +export default class Expandable extends PureComponent { + 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 ( +
+
+ {children} +
+
+ ) + } +} diff --git a/src/renderer/component/fileDetails/index.js b/src/renderer/component/fileDetails/index.js index fb92ce86f23..de2a0883d47 100644 --- a/src/renderer/component/fileDetails/index.js +++ b/src/renderer/component/fileDetails/index.js @@ -4,8 +4,12 @@ import { makeSelectContentTypeForUri, makeSelectMetadataForUri, makeSelectFileInfoForUri, + doNotify } from 'lbry-redux'; +import { selectUser } from 'lbryinc'; import { doOpenFileInFolder } from 'redux/actions/file'; +import { selectHasClickedComment } from 'redux/selectors/app'; +import { doClickCommentButton } from 'redux/actions/app'; import FileDetails from './view'; const select = (state, props) => ({ @@ -13,10 +17,14 @@ const select = (state, props) => ({ contentType: makeSelectContentTypeForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state), + hasClickedComment: selectHasClickedComment(state), + user: selectUser(state), }); const perform = dispatch => ({ openFolder: path => dispatch(doOpenFileInFolder(path)), + showSnackBar: (message) => dispatch(doNotify({ message, displayType: ['snackbar'], })), + clickCommentButton: () => dispatch(doClickCommentButton()) }); export default connect( diff --git a/src/renderer/component/fileDetails/view.jsx b/src/renderer/component/fileDetails/view.jsx index f36cad539d4..9ce830749f4 100644 --- a/src/renderer/component/fileDetails/view.jsx +++ b/src/renderer/component/fileDetails/view.jsx @@ -1,9 +1,11 @@ // @flow -import * as React from 'react'; +import type { Claim } from 'types/claim'; +import React, { Fragment, PureComponent } from 'react'; +import { Lbryio } from 'lbryinc'; import MarkdownPreview from 'component/common/markdown-preview'; import Button from 'component/button'; import path from 'path'; -import type { Claim } from 'types/claim'; +import Expandable from 'component/expandable'; type Props = { claim: Claim, @@ -19,59 +21,91 @@ type Props = { contentType: string, }; -const FileDetails = (props: Props) => { - const { claim, contentType, fileInfo, metadata, openFolder } = props; +class FileDetails extends PureComponent { + constructor() { + super(); + this.handleCommentClick = this.handleCommentClick.bind(this); + } - if (!claim || !metadata) { - return ( -
- {__('Empty claim or metadata info.')} -
- ); + handleCommentClick() { + const { clickCommentButton, showSnackBar } = this.props; + + clickCommentButton() + Lbryio.call('user_tag', 'edit', { add: 'comments-waitlist' }) + showSnackBar(__('Thanks! This feature is so beta, all we had time for was this button.')) } - const { description, language, license } = metadata; + render() { + const { claim, contentType, fileInfo, metadata, openFolder, showSnackBar, hasClickedComment, user } = this.props; + + if (!claim || !metadata) { + return ( +
+ {__('Empty claim or metadata info.')} +
+ ); + } + + const { description, language, license } = metadata; - const mediaType = contentType || 'unknown'; - const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null; + const mediaType = contentType || 'unknown'; + const downloadPath = fileInfo ? path.normalize(fileInfo.download_path) : null; - return ( - - {description && ( - -
About
+ return ( + + + {description && ( + +
About
+
+ +
+
+ )} +
Info
- +
+ {__('Content-Type')} + {': '} + {mediaType} +
+
+ {__('Language')} + {': '} + {language} +
+
+ {__('License')} + {': '} + {license} +
+ {downloadPath && ( +
+ {__('Downloaded to')} + {': '} +
+ )}
-
- )} -
Info
-
-
- {__('Content-Type')} - {': '} - {mediaType} -
-
- {__('Language')} - {': '} - {language} -
-
- {__('License')} - {': '} - {license} -
- {downloadPath && ( -
- {__('Downloaded to')} - {': '} -
- )} -
-
- ); -}; + {hasClickedComment && ( +

+ {user ? ( + __("Your support has been added. You will be notified when comments are available.") + ) : ( + __("Your support has been added. Comments are coming soon.") + )} +

+ )} + + + ); + } +} export default FileDetails; diff --git a/src/renderer/constants/action_types.js b/src/renderer/constants/action_types.js index 009eaa0f3b3..9c578e08cc3 100644 --- a/src/renderer/constants/action_types.js +++ b/src/renderer/constants/action_types.js @@ -13,6 +13,7 @@ export const DAEMON_READY = 'DAEMON_READY'; export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH'; export const DAEMON_VERSION_MISMATCH = 'DAEMON_VERSION_MISMATCH'; export const VOLUME_CHANGED = 'VOLUME_CHANGED'; +export const ADD_COMMENT = 'ADD_COMMENT'; // Navigation 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 AUTO_UPDATE_DECLINED = 'AUTO_UPDATE_DECLINED'; export const AUTO_UPDATE_DOWNLOADED = 'AUTO_UPDATE_DOWNLOADED'; +export const CLEAR_UPGRADE_TIMER = 'CLEAR_UPGRADE_TIMER'; // Wallet export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED'; diff --git a/src/renderer/index.js b/src/renderer/index.js index 5ed612fd0f8..4f79a6dc27c 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -123,11 +123,16 @@ document.addEventListener('drop', event => { }); document.addEventListener('click', event => { let { target } = event; + while (target && target !== document) { if (target.matches('a') || target.matches('button')) { // TODO: Look into using accessiblity labels (this would also make the app more accessible) 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) { analytics.track('CLICK', { target: element, diff --git a/src/renderer/redux/actions/app.js b/src/renderer/redux/actions/app.js index 61e5b42f0a8..db973b3ce43 100644 --- a/src/renderer/redux/actions/app.js +++ b/src/renderer/redux/actions/app.js @@ -2,8 +2,8 @@ import { execSync } from 'child_process'; import isDev from 'electron-is-dev'; import path from 'path'; import { ipcRenderer, remote } from 'electron'; +import * as ACTIONS from 'constants/action_types'; import { - ACTIONS, Lbry, doBalanceSubscribe, doFetchFileInfosAndPublishedClaims, @@ -387,6 +387,12 @@ export function doChangeVolume(volume) { }; } +export function doClickCommentButton() { + return { + type: ACTIONS.ADD_COMMENT, + }; +} + export function doConditionalAuthNavigate(newSession) { return (dispatch, getState) => { const state = getState(); diff --git a/src/renderer/redux/reducers/app.js b/src/renderer/redux/reducers/app.js index 2aa59f98d79..aa1fd72c37d 100644 --- a/src/renderer/redux/reducers/app.js +++ b/src/renderer/redux/reducers/app.js @@ -33,7 +33,7 @@ export type AppState = { checkUpgradeTimer: ?number, isUpgradeAvailable: ?boolean, isUpgradeSkipped: ?boolean, - snackBar: ?SnackBar, + hasClickedComment: boolean, }; const defaultState: AppState = { @@ -50,14 +50,13 @@ const defaultState: AppState = { autoUpdateDownloaded: false, autoUpdateDeclined: false, modalsAllowed: true, - + hasClickedComment: false, downloadProgress: undefined, upgradeDownloading: undefined, upgradeDownloadComplete: undefined, checkUpgradeTimer: undefined, isUpgradeAvailable: undefined, isUpgradeSkipped: undefined, - snackBar: undefined, }; reducers[ACTIONS.DAEMON_READY] = state => @@ -189,6 +188,11 @@ reducers[ACTIONS.CLEAR_UPGRADE_TIMER] = state => checkUpgradeTimer: undefined, }); +reducers[ACTIONS.ADD_COMMENT] = state => + Object.assign({}, state, { + hasClickedComment: true, + }); + export default function reducer(state: AppState = defaultState, action: any) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/src/renderer/redux/selectors/app.js b/src/renderer/redux/selectors/app.js index 1f1655ea2c1..8fc21965f53 100644 --- a/src/renderer/redux/selectors/app.js +++ b/src/renderer/redux/selectors/app.js @@ -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 selectIsUpgradeAvailable = createSelector( diff --git a/src/renderer/scss/_gui.scss b/src/renderer/scss/_gui.scss index 751fc846390..47537dd8365 100644 --- a/src/renderer/scss/_gui.scss +++ b/src/renderer/scss/_gui.scss @@ -130,7 +130,7 @@ p:not(:first-of-type) { bottom: 0; right: 0; - background-color: rgba($lbry-gray-1, 0.3); + background-color: mix($lbry-white, $lbry-gray-1, 70%); display: flex; position: absolute; z-index: 0; @@ -245,11 +245,6 @@ p:not(:first-of-type) { padding: 0; } -.divider__horizontal { - border-top: $lbry-gray-2; - margin: 16px 0; -} - .hidden { display: none; } diff --git a/src/renderer/scss/all.scss b/src/renderer/scss/all.scss index 8b421609c55..8fa28771a81 100644 --- a/src/renderer/scss/all.scss +++ b/src/renderer/scss/all.scss @@ -6,4 +6,4 @@ 'component/markdown-editor', 'component/scrollbar', 'component/spinner', 'component/nav', 'component/file-list', 'component/file-render', 'component/search', 'component/toggle', 'component/dat-gui', 'component/item-list', 'component/time', 'component/icon', - 'component/placeholder', 'component/badge', 'themes/dark'; + 'component/placeholder', 'component/badge', 'component/expandable', 'themes/dark'; diff --git a/src/renderer/scss/component/_expandable.scss b/src/renderer/scss/component/_expandable.scss new file mode 100644 index 00000000000..c62a85c8cef --- /dev/null +++ b/src/renderer/scss/component/_expandable.scss @@ -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% + ); +} diff --git a/src/renderer/scss/themes/_dark.scss b/src/renderer/scss/themes/_dark.scss index 6248f3e9f3a..94863ede349 100644 --- a/src/renderer/scss/themes/_dark.scss +++ b/src/renderer/scss/themes/_dark.scss @@ -83,7 +83,7 @@ html[data-theme='dark'] { } // - // BUTTON + // Button // .btn { &.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%); + } } diff --git a/src/renderer/store.js b/src/renderer/store.js index b6e19188594..d2237413d61 100644 --- a/src/renderer/store.js +++ b/src/renderer/store.js @@ -108,6 +108,7 @@ const fileInfoFilter = createFilter('fileInfo', [ 'fileListDownloadedSort', 'fileListSubscriptionSort', ]); +const appFilter = createFilter('app', ['hasClickedComment']); // We only need to persist the receiveAddress for the wallet const walletFilter = createFilter('wallet', ['receiveAddress']); @@ -115,7 +116,14 @@ const persistOptions = { whitelist: ['subscriptions', 'publish', 'wallet', 'content', 'fileInfo'], // Order is important. Needs to be compressed last or other transforms can't // read the data - transforms: [subscriptionsFilter, walletFilter, contentFilter, fileInfoFilter, compressor], + transforms: [ + subscriptionsFilter, + walletFilter, + contentFilter, + fileInfoFilter, + appFilter, + compressor, + ], debounce: 10000, storage: localForage, };