diff --git a/package.json b/package.json index 95e77d5354a8e1a364b62c9d97c134b480f49a1e..756cc1b1369ed6f08444b0523b3c6f547887f23e 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "foundation-sites": "git+https://gitlab.syncad.com/hive/foundation-sites.git#544fb8a26efcf45e9ba9ccaa77b04d0ae35b9722", "hive-uri": "^0.2.3", "hivesigner": "3.2.7", + "humanize-duration": "^3.27.0", "humanize-number": "0.0.2", "immutable": "^3.8.1", "intl": "1.2.5", @@ -94,6 +95,7 @@ "react-addons-pure-render-mixin": "15.4.2", "react-autocomplete": "1.8.1", "react-copy-to-clipboard": "4.3.1", + "react-dispatch": "^1.2.0", "react-dom": "16.8", "react-dropzone": "3.13.4", "react-headroom": "2.2.7", @@ -110,6 +112,7 @@ "react-rte": "^0.16.3", "react-sparklines": "1.7.0", "react-tabs": "^3.1.1", + "react-tooltip-lite": "^1.12.0", "redux": "3.7.2", "redux-form": "5.3.4", "redux-saga": "0.16.0", diff --git a/src/app/components/App.scss b/src/app/components/App.scss index 909525696c11031003b929fe358b0a5efba9f62b..3c40a47b634c840b5ed13183352552026fcc4d35 100644 --- a/src/app/components/App.scss +++ b/src/app/components/App.scss @@ -213,3 +213,12 @@ ); } } + +.react-tooltip-lite { + background: #333; + color: white; +} + +.react-tooltip-lite-arrow { + border-color: #333; +} diff --git a/src/app/components/all.scss b/src/app/components/all.scss index ea3465ed3bfefdab9b8f521cb6729c8041e4af39..c01ccd776142cae5bdcb3317e553e6be6310ad31 100644 --- a/src/app/components/all.scss +++ b/src/app/components/all.scss @@ -13,6 +13,7 @@ @import "./elements/Icon"; @import "./elements/LoadingIndicator"; @import "./elements/Userpic"; +@import "./elements/UserpicInfoWrapper"; @import "./elements/Voting"; @import "./elements/FormattedAsset"; @import "./elements/ReplyEditor"; diff --git a/src/app/components/elements/ReplyEditor.jsx b/src/app/components/elements/ReplyEditor.jsx index 5206895182520d0c8a4b016d8545e0df85c3eea4..30d870a2f7ed2fef4c9304789882e437e3775271 100644 --- a/src/app/components/elements/ReplyEditor.jsx +++ b/src/app/components/elements/ReplyEditor.jsx @@ -5,6 +5,7 @@ import reactForm from 'app/utils/ReactForm'; import _ from 'lodash'; import { connect } from 'react-redux'; import classnames from 'classnames'; +import Tooltip from 'react-tooltip-lite'; import * as transactionActions from 'app/redux/TransactionReducer'; import * as userActions from 'app/redux/UserReducer'; import MarkdownViewer from 'app/components/cards/MarkdownViewer'; @@ -12,7 +13,6 @@ import TagInput, { validateTagInput } from 'app/components/cards/TagInput'; import { extractRtags } from 'app/utils/ExtractContent'; import LoadingIndicator from 'app/components/elements/LoadingIndicator'; import PostCategoryBanner from 'app/components/elements/PostCategoryBanner'; -import Tooltip from 'app/components/elements/Tooltip'; import sanitizeConfig, { allowedTags } from 'app/utils/SanitizeConfig'; import sanitize from 'sanitize-html'; import HtmlReady from 'shared/HtmlReady'; @@ -22,7 +22,11 @@ import Dropzone from 'react-dropzone'; import tt from 'counterpart'; import { loadUserTemplates, saveUserTemplates } from 'app/utils/UserTemplates'; import BadActorList from 'app/utils/BadActorList'; +import { FormattedHTMLMessage } from 'app/Translator'; +import { api } from "@hiveio/hive-js"; +import HumanizeDuration from "humanize-duration"; import VisualEditor from './VisualEditor'; +import { calculateRcStats } from "../../utils/UserUtil"; const remarkable = new Remarkable({ html: true, linkify: false, breaks: true }); @@ -87,10 +91,20 @@ class ReplyEditor extends React.Component { progress: {}, imagesUploadCount: 0, enableSideBySide: true, + userRc: undefined, }; this.initForm(props); } + async getUserRc(username) { + const res = await api.callAsync('rc_api.find_rc_accounts', { accounts: [username] }); + const rcAccounts = _.get(res, 'rc_accounts'); + + if (rcAccounts) { + this.setState({ userRc: rcAccounts[0] }); + } + } + componentWillMount() { const { formId } = this.props; @@ -194,6 +208,9 @@ class ReplyEditor extends React.Component { } componentDidMount() { + const { username } = this.props; + this.getUserRc(username); + setTimeout(() => { if (this.refs.rte) this.refs.rte._focus(); else if (this.props.isStory) this.refs.titleRef.focus(); @@ -606,13 +623,25 @@ class ReplyEditor extends React.Component { maxAcceptedPayout, } = this.props; const { - submitting, valid, handleSubmit, resetForm + submitting, valid, handleSubmit, resetForm, } = this.state.replyForm; - const { postError, rte } = this.state; + const { postError, rte, userRc } = this.state; const { progress, noClipboardData } = this.state; const disabled = submitting || !valid; const loading = submitting || this.state.loading; + let accountStats; + let rcStats = ''; + if (userRc) { + accountStats = calculateRcStats(userRc); + const { resourceCreditsPercent, resourceCreditsWaitTime } = accountStats; + rcStats = tt('g.rcLevel', { rc_percent: resourceCreditsPercent }); + + if (resourceCreditsWaitTime > 0) { + rcStats += ` ${tt('g.rcFullIn', { duration: HumanizeDuration(resourceCreditsWaitTime * 1000, { largest: 2 }) })}`; + } + } + let selectedCoverImage = ''; const jsonMetadataImages = _.get(jsonMetadata, 'image', []); if (jsonMetadataImages && jsonMetadataImages.length > 0) { @@ -659,7 +688,7 @@ class ReplyEditor extends React.Component { errorCallback, }; const postLabel = username ? ( - <Tooltip t={tt('g.post_as_user', { username })}>{tt('g.post')}</Tooltip> + <Tooltip content={tt('g.post_as_user', { username })}>{tt('g.post')}</Tooltip> ) : ( tt('g.post') ); @@ -985,16 +1014,39 @@ class ReplyEditor extends React.Component { </span> )} </div> - <a href="#" onClick={this.showAdvancedSettings}> - {tt('reply_editor.advanced_settings')} - </a> + <Tooltip + content={<FormattedHTMLMessage id="reply_editor.advanced_tooltip" />} + arrow={false} + > + <a href="#" onClick={this.showAdvancedSettings}> + {tt('reply_editor.advanced_settings')} + </a> + </Tooltip> {' '} <br /> - </div> </div> )} </div> + {rcStats && ( + <div className={vframe_section_shrink_class} style={{ marginTop: '0.5rem' }}> + <div className="ReplyEditor__options"> + <h5> + Account stats: + </h5> + <div> + <Tooltip + content={<FormattedHTMLMessage id="reply_editor.rc_tooltip" />} + arrow={false} + > + {rcStats} + </Tooltip> + </div> + {' '} + <br /> + </div> + </div> + )} <div className={vframe_section_shrink_class}> {postError && <div className="error">{postError}</div>} diff --git a/src/app/components/elements/UserpicInfoWrapper.jsx b/src/app/components/elements/UserpicInfoWrapper.jsx new file mode 100644 index 0000000000000000000000000000000000000000..384ec2dc480939cac78b7f8253ce0e01f9f7111c --- /dev/null +++ b/src/app/components/elements/UserpicInfoWrapper.jsx @@ -0,0 +1,91 @@ +import React from 'react'; +import classnames from 'classnames'; +import tt from 'counterpart'; +import HumanizeDuration from 'humanize-duration'; +import Tooltip from 'react-tooltip-lite'; +import _ from 'lodash'; +import { dispatcher } from 'react-dispatch'; +import { connect } from 'react-redux'; +import { api } from '@hiveio/hive-js'; +import { calculateRcStats } from 'app/utils/UserUtil'; +import { EVENT_OPERATION_BROADCAST } from 'shared/constants'; + +class UserInfoToolbar extends React.Component { + constructor(props) { + super(props); + this.state = { userRc: undefined }; + this.listenerActive = null; + } + + async getUserRc(username) { + const res = await api.callAsync('rc_api.find_rc_accounts', { accounts: [username] }); + const rcAccounts = _.get(res, 'rc_accounts'); + + if (rcAccounts) { + this.setState({ userRc: rcAccounts[0] }); + } + } + + componentDidMount() { + dispatcher.on(EVENT_OPERATION_BROADCAST, () => { + console.log('EVENT_OPERATION_BROADCAST event received'); + this.getUserRc(currentUser); + }); + + const { currentUser } = this.props; + this.getUserRc(currentUser); + } + + componentWillUnmount() { + dispatcher.off(EVENT_OPERATION_BROADCAST); + } + + render() { + const { children } = this.props; + const { userRc } = this.state; + + if (!userRc) { + return children; + } + + const accountStats = calculateRcStats(userRc); + const { resourceCreditsPercent, resourceCreditsWaitTime } = accountStats; + + let rcWaitTimeMesssage = ''; + if (resourceCreditsWaitTime > 0) { + rcWaitTimeMesssage = tt('g.rcFullIn', { duration: HumanizeDuration(resourceCreditsWaitTime * 1000, { largest: 2 })}); + } + + return ( + <Tooltip + content={`${tt('g.rcLevel', { rc_percent: resourceCreditsPercent})} ${rcWaitTimeMesssage}`} + eventOff="onClick" + > + <div + className={classnames( + 'Userpic__infowrapper', + 'progress-circle', + `p${accountStats.resourceCreditsPercent}`, + accountStats.resourceCreditsPercent > 50 && 'over50', + )} + > + <span>{children}</span> + <div className="left-half-clipper"> + <div className="first50-bar" /> + <div className="value-bar" /> + </div> + </div> + </Tooltip> + ); + } +} + +export { UserInfoToolbar as _Header_ }; + +export default connect((state) => { + const currentUser = state.user.getIn(['current', 'username']); + return { + currentUser, + profile: state.userProfiles.getIn(['profiles', currentUser]), + }; +})(UserInfoToolbar); diff --git a/src/app/components/elements/UserpicInfoWrapper.scss b/src/app/components/elements/UserpicInfoWrapper.scss new file mode 100644 index 0000000000000000000000000000000000000000..f975227b04b2651e04d9523a7377b863e4d36d2e --- /dev/null +++ b/src/app/components/elements/UserpicInfoWrapper.scss @@ -0,0 +1,198 @@ +.Userpic__infowrapper { + .UserInfo__rc_innercircle { + position: absolute; + z-index: 6; + top: 50%; + left: 50%; + width: 40px; + height: 40px; + margin: -20px 0 0 -20px; + background: red; + border-radius: 100%; + } +} + +.Header__userpic .Userpic__infowrapper .Userpic { + width: 34px; + height: 34px; + margin-top: 5px; + margin-left: 0; +} + +/* https://www.cssscript.com/circular-progress-bar-plain-html-css/ */ +.progress-circle { + font-size: 8px; + margin: -2px 0 0 0; + position: relative; /* so that children can be absolutely positioned */ + padding: 0; + width: 5em; + height: 5em; + background-color: #F2E9E1; + border-radius: 50%; + line-height: 5em; +} + +.progress-circle:after{ + border: none; + position: absolute; + top: 0.35em; + left: 0.35em; + text-align: center; + display: block; + border-radius: 50%; + width: 4.3em; + height: 4.3em; + background-color: white; + content: " "; +} +/* Text inside the control */ +.progress-circle span { + position: absolute; + line-height: 5em; + width: 5em; + text-align: center; + display: block; + z-index: 2; + color: seagreen; +} +.left-half-clipper { + /* a round circle */ + border-radius: 50%; + width: 5em; + height: 5em; + position: absolute; /* needed for clipping */ + clip: rect(0, 5em, 5em, 2.5em); /* clips the whole left half*/ +} +/* when p>50, don't clip left half*/ +.progress-circle.over50 .left-half-clipper { + clip: rect(auto,auto,auto,auto); +} +.value-bar { + /*This is an overlayed square, that is made round with the border radius, + then it is cut to display only the left half, then rotated clockwise + to escape the outer clipping path.*/ + position: absolute; /*needed for clipping*/ + clip: rect(0, 2.5em, 5em, 0); + width: 5em; + height: 5em; + border-radius: 50%; + border: 0.45em solid seagreen; /*The border is 0.35 but making it larger removes visual artifacts */ + /*background-color: #4D642D;*/ /* for debug */ + box-sizing: border-box; + +} +/* Progress bar filling the whole right half for values above 50% */ +.progress-circle.over50 .first50-bar { + /*Progress bar for the first 50%, filling the whole right half*/ + position: absolute; /*needed for clipping*/ + clip: rect(0, 5em, 5em, 2.5em); + background-color: seagreen; + border-radius: 50%; + width: 5em; + height: 5em; +} +.progress-circle:not(.over50) .first50-bar{ display: none; } + + +/* Progress bar rotation position */ +.progress-circle.p0 .value-bar { display: none; } +.progress-circle.p1 .value-bar { transform: rotate(4deg); } +.progress-circle.p2 .value-bar { transform: rotate(7deg); } +.progress-circle.p3 .value-bar { transform: rotate(11deg); } +.progress-circle.p4 .value-bar { transform: rotate(14deg); } +.progress-circle.p5 .value-bar { transform: rotate(18deg); } +.progress-circle.p6 .value-bar { transform: rotate(22deg); } +.progress-circle.p7 .value-bar { transform: rotate(25deg); } +.progress-circle.p8 .value-bar { transform: rotate(29deg); } +.progress-circle.p9 .value-bar { transform: rotate(32deg); } +.progress-circle.p10 .value-bar { transform: rotate(36deg); } +.progress-circle.p11 .value-bar { transform: rotate(40deg); } +.progress-circle.p12 .value-bar { transform: rotate(43deg); } +.progress-circle.p13 .value-bar { transform: rotate(47deg); } +.progress-circle.p14 .value-bar { transform: rotate(50deg); } +.progress-circle.p15 .value-bar { transform: rotate(54deg); } +.progress-circle.p16 .value-bar { transform: rotate(58deg); } +.progress-circle.p17 .value-bar { transform: rotate(61deg); } +.progress-circle.p18 .value-bar { transform: rotate(65deg); } +.progress-circle.p19 .value-bar { transform: rotate(68deg); } +.progress-circle.p20 .value-bar { transform: rotate(72deg); } +.progress-circle.p21 .value-bar { transform: rotate(76deg); } +.progress-circle.p22 .value-bar { transform: rotate(79deg); } +.progress-circle.p23 .value-bar { transform: rotate(83deg); } +.progress-circle.p24 .value-bar { transform: rotate(86deg); } +.progress-circle.p25 .value-bar { transform: rotate(90deg); } +.progress-circle.p26 .value-bar { transform: rotate(94deg); } +.progress-circle.p27 .value-bar { transform: rotate(97deg); } +.progress-circle.p28 .value-bar { transform: rotate(101deg); } +.progress-circle.p29 .value-bar { transform: rotate(104deg); } +.progress-circle.p30 .value-bar { transform: rotate(108deg); } +.progress-circle.p31 .value-bar { transform: rotate(112deg); } +.progress-circle.p32 .value-bar { transform: rotate(115deg); } +.progress-circle.p33 .value-bar { transform: rotate(119deg); } +.progress-circle.p34 .value-bar { transform: rotate(122deg); } +.progress-circle.p35 .value-bar { transform: rotate(126deg); } +.progress-circle.p36 .value-bar { transform: rotate(130deg); } +.progress-circle.p37 .value-bar { transform: rotate(133deg); } +.progress-circle.p38 .value-bar { transform: rotate(137deg); } +.progress-circle.p39 .value-bar { transform: rotate(140deg); } +.progress-circle.p40 .value-bar { transform: rotate(144deg); } +.progress-circle.p41 .value-bar { transform: rotate(148deg); } +.progress-circle.p42 .value-bar { transform: rotate(151deg); } +.progress-circle.p43 .value-bar { transform: rotate(155deg); } +.progress-circle.p44 .value-bar { transform: rotate(158deg); } +.progress-circle.p45 .value-bar { transform: rotate(162deg); } +.progress-circle.p46 .value-bar { transform: rotate(166deg); } +.progress-circle.p47 .value-bar { transform: rotate(169deg); } +.progress-circle.p48 .value-bar { transform: rotate(173deg); } +.progress-circle.p49 .value-bar { transform: rotate(176deg); } +.progress-circle.p50 .value-bar { transform: rotate(180deg); } +.progress-circle.p51 .value-bar { transform: rotate(184deg); } +.progress-circle.p52 .value-bar { transform: rotate(187deg); } +.progress-circle.p53 .value-bar { transform: rotate(191deg); } +.progress-circle.p54 .value-bar { transform: rotate(194deg); } +.progress-circle.p55 .value-bar { transform: rotate(198deg); } +.progress-circle.p56 .value-bar { transform: rotate(202deg); } +.progress-circle.p57 .value-bar { transform: rotate(205deg); } +.progress-circle.p58 .value-bar { transform: rotate(209deg); } +.progress-circle.p59 .value-bar { transform: rotate(212deg); } +.progress-circle.p60 .value-bar { transform: rotate(216deg); } +.progress-circle.p61 .value-bar { transform: rotate(220deg); } +.progress-circle.p62 .value-bar { transform: rotate(223deg); } +.progress-circle.p63 .value-bar { transform: rotate(227deg); } +.progress-circle.p64 .value-bar { transform: rotate(230deg); } +.progress-circle.p65 .value-bar { transform: rotate(234deg); } +.progress-circle.p66 .value-bar { transform: rotate(238deg); } +.progress-circle.p67 .value-bar { transform: rotate(241deg); } +.progress-circle.p68 .value-bar { transform: rotate(245deg); } +.progress-circle.p69 .value-bar { transform: rotate(248deg); } +.progress-circle.p70 .value-bar { transform: rotate(252deg); } +.progress-circle.p71 .value-bar { transform: rotate(256deg); } +.progress-circle.p72 .value-bar { transform: rotate(259deg); } +.progress-circle.p73 .value-bar { transform: rotate(263deg); } +.progress-circle.p74 .value-bar { transform: rotate(266deg); } +.progress-circle.p75 .value-bar { transform: rotate(270deg); } +.progress-circle.p76 .value-bar { transform: rotate(274deg); } +.progress-circle.p77 .value-bar { transform: rotate(277deg); } +.progress-circle.p78 .value-bar { transform: rotate(281deg); } +.progress-circle.p79 .value-bar { transform: rotate(284deg); } +.progress-circle.p80 .value-bar { transform: rotate(288deg); } +.progress-circle.p81 .value-bar { transform: rotate(292deg); } +.progress-circle.p82 .value-bar { transform: rotate(295deg); } +.progress-circle.p83 .value-bar { transform: rotate(299deg); } +.progress-circle.p84 .value-bar { transform: rotate(302deg); } +.progress-circle.p85 .value-bar { transform: rotate(306deg); } +.progress-circle.p86 .value-bar { transform: rotate(310deg); } +.progress-circle.p87 .value-bar { transform: rotate(313deg); } +.progress-circle.p88 .value-bar { transform: rotate(317deg); } +.progress-circle.p89 .value-bar { transform: rotate(320deg); } +.progress-circle.p90 .value-bar { transform: rotate(324deg); } +.progress-circle.p91 .value-bar { transform: rotate(328deg); } +.progress-circle.p92 .value-bar { transform: rotate(331deg); } +.progress-circle.p93 .value-bar { transform: rotate(335deg); } +.progress-circle.p94 .value-bar { transform: rotate(338deg); } +.progress-circle.p95 .value-bar { transform: rotate(342deg); } +.progress-circle.p96 .value-bar { transform: rotate(346deg); } +.progress-circle.p97 .value-bar { transform: rotate(349deg); } +.progress-circle.p98 .value-bar { transform: rotate(353deg); } +.progress-circle.p99 .value-bar { transform: rotate(356deg); } +.progress-circle.p100 .value-bar { transform: rotate(360deg); } diff --git a/src/app/components/modules/Header/index.jsx b/src/app/components/modules/Header/index.jsx index 2c3003bbcd6c9fb45694f78eabcebaed94cab7c5..afed304b0b20df11993f48b8095fa1a94d1aa3a1 100644 --- a/src/app/components/modules/Header/index.jsx +++ b/src/app/components/modules/Header/index.jsx @@ -15,6 +15,7 @@ import * as appActions from 'app/redux/AppReducer'; import { startPolling } from 'app/redux/PollingSaga'; import { actions as fetchDataSagaActions } from 'app/redux/FetchDataSaga'; import Userpic from 'app/components/elements/Userpic'; +import UserpicInfoWrapper from 'app/components/elements/UserpicInfoWrapper'; import { SIGNUP_URL } from 'shared/constants'; import SteemLogo from 'app/components/elements/SteemLogo'; import Announcement from 'app/components/elements/Announcement'; @@ -301,7 +302,9 @@ class Header extends React.Component { position="left" > <li className="Header__userpic "> - <Userpic account={username} /> + <UserpicInfoWrapper> + <Userpic account={username} /> + </UserpicInfoWrapper> </li> {!notificationActionPending && unreadNotificationCount > 0 && ( <div className="Header__notification"> diff --git a/src/app/components/modules/Header/styles.scss b/src/app/components/modules/Header/styles.scss index acbec17dc674f23bf61307814895e8c0a1f366a6..9f2fc2374c52159d59bc3fc03f2508191ff95401 100644 --- a/src/app/components/modules/Header/styles.scss +++ b/src/app/components/modules/Header/styles.scss @@ -1,14 +1,14 @@ .Header { - backface-visibility: hidden; - top: 0; - left: 0; - width: 100%; - z-index: 100; - box-shadow: 0 2px 4px 0 rgba(0,0,0,0.05); - @include themify($themes) { - background-color: themed('navBackgroundColor'); - border-bottom: themed('border'); - } + backface-visibility: hidden; + top: 0; + left: 0; + width: 100%; + z-index: 100; + box-shadow: 0 2px 4px 0 rgba(0,0,0,0.05); + @include themify($themes) { + background-color: themed('navBackgroundColor'); + border-bottom: themed('border'); + } } .Header__nav { @@ -19,26 +19,26 @@ } .ConnectionError { - margin-right: 4rem; - color: #ec5840; + margin-right: 4rem; + color: #ec5840; } .Header__logotype { - transition: 0.2s all ease-in-out; - height: 37px; - display: flex; - align-items: baseline; - .icon-svg { - @include themify($themes) { - fill: themed('colorAccent'); + transition: 0.2s all ease-in-out; + height: 37px; + display: flex; + align-items: baseline; + .icon-svg { + @include themify($themes) { + fill: themed('colorAccent'); + } } - } - &-beta{ - position: absolute; - top: 38px; - left: 136px; - } + &-beta{ + position: absolute; + top: 38px; + left: 136px; + } } .Header__sort { @@ -98,16 +98,18 @@ display: block; width: 36px; height: 36px; - .Userpic { - width: 36px !important; - height: 36px !important; - @include MQ(M) { - width: 40px!important; - height: 40px !important; - position: relative; - top: -2px; + position: relative; + .Userpic { + width: 36px; + height: 36px; + + @include MQ(M) { + width: 40px; + height: 40px; + position: relative; + top: -2px; + } } - } } .Header__notification { @@ -121,11 +123,12 @@ line-height: 20px; font-size: 11px; text-align: center; + z-index: 2; > span { color: white; } &--loading { - background: transparent; + background: transparent; } } @@ -143,45 +146,45 @@ span.Header__hamburger.toggle-menu { } cursor: pointer; &::after { - transition: 0.2s all ease-in-out; - @include themify($themes) { - background: themed('textColorPrimary'); - box-shadow: 0 7px 0 themed('textColorPrimary'), 0 14px 0 themed('textColorPrimary'); - } + transition: 0.2s all ease-in-out; + @include themify($themes) { + background: themed('textColorPrimary'); + box-shadow: 0 7px 0 themed('textColorPrimary'), 0 14px 0 themed('textColorPrimary'); + } } &:hover { - &::after { - @include themify($themes) { - background: themed('textColorAccent'); - box-shadow: 0 7px 0 themed('textColorAccent'), 0 14px 0 themed('textColorAccent'); + &::after { + @include themify($themes) { + background: themed('textColorAccent'); + box-shadow: 0 7px 0 themed('textColorAccent'), 0 14px 0 themed('textColorAccent'); + } } - } } } .annoucement-banner { - text-align: left; - position: relative; - background: #171FC9; //Notice Blue - // background: #fff3cd; - color: #fff; - // color: #856404; - @include themify($themes) { - border-bottom: themed('borderLight'); - } - @include MQ(M) { - text-align: center; - } - .close-button { - position: absolute; - top: 2px; - right: 0px; - transform: scale(0.85); + text-align: left; + position: relative; + background: #171FC9; //Notice Blue + // background: #fff3cd; color: #fff; - &:hover, &:focus { - color: #ccc; + // color: #856404; + @include themify($themes) { + border-bottom: themed('borderLight'); + } + @include MQ(M) { + text-align: center; + } + .close-button { + position: absolute; + top: 2px; + right: 0px; + transform: scale(0.85); + color: #fff; + &:hover, &:focus { + color: #ccc; + } } - } } .announcement-banner__text { @@ -190,10 +193,10 @@ span.Header__hamburger.toggle-menu { font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.3; - } +} .announcement-banner__link { - color: #fafafa !important; - // color: #856404 !important;; - text-decoration: underline; + color: #fafafa !important; + // color: #856404 !important;; + text-decoration: underline; } diff --git a/src/app/locales/en.json b/src/app/locales/en.json index 0ff13e97ab29ba5db44a443e64f691cb41aeb5c5..89c33f408cfbafee445a1850d4d468c103fbd3c6 100644 --- a/src/app/locales/en.json +++ b/src/app/locales/en.json @@ -255,7 +255,9 @@ "confirm": "You are about to publish a HIVE private key or master password. You will probably lose control of the associated account and all its funds.", "warning": "Legitimate users, including employees of Hive, will never ask you for a private key or master password.", "checkbox": "I understand" - } + }, + "rcLevel": "Resource Credits (RC) level: %(rc_percent)s%%.", + "rcFullIn": "Full in %(duration)s" }, "navigation": { "about": "About Hive", @@ -331,7 +333,9 @@ "select_cover_image": "Select your cover image", "alt_author": "Author (if different from current account)", "shorten_alt_author": "Shorten the author name", - "invalid_username": "Invalid username (no @ required)" + "invalid_username": "Invalid username (no @ required)", + "rc_tooltip": "Resource Credits are required to perform any operation on the blockchain.<br/>They progressively recharge at a rate of 20%% per day.", + "advanced_tooltip": "Here you can set post options such as author rewards split,<br/>max accepted payout, beneficiaries & post templates" }, "beneficiary_selector_jsx": { "header": "Who should receive any rewards?", diff --git a/src/app/redux/GlobalReducer.js b/src/app/redux/GlobalReducer.js index ed3fe9c442fe909799a646f15f37b24041398a8a..452eee23f163eeff3ff4d363e07161105f777d9d 100644 --- a/src/app/redux/GlobalReducer.js +++ b/src/app/redux/GlobalReducer.js @@ -240,11 +240,14 @@ export default function reducer(state = defaultState, action = {}) { case VOTED: { const { - voter, author, permlink, weight -} = payload; + voter, author, permlink, weight + } = payload; const vote = Map({ voter, percent: weight }); const _key = ['content', author + '/' + permlink, 'active_votes']; let votes = state.getIn(_key, List()); + if (votes === null) { + votes = List(); + } const idx = votes.findIndex((v) => v.get('voter') === voter); votes = idx === -1 ? votes.push(vote) : votes.set(idx, vote); diff --git a/src/app/redux/TransactionSaga.js b/src/app/redux/TransactionSaga.js index 2b203026cc5bc936861d2c0e7b3941504e199abb..25dcbe47e89a13a6b5a5b25c0e14d72553391f5e 100644 --- a/src/app/redux/TransactionSaga.js +++ b/src/app/redux/TransactionSaga.js @@ -10,7 +10,9 @@ import base58 from 'bs58'; import secureRandom from 'secure-random'; import { PrivateKey } from '@hiveio/hive-js/lib/auth/ecc'; import { broadcast } from '@hiveio/hive-js'; +import { dispatcher } from 'react-dispatch'; +import { EVENT_OPERATION_BROADCAST } from 'shared/constants'; import { getContent } from 'app/redux/SagaShared'; import { postingOps, findSigningKey } from 'app/redux/AuthSaga'; import * as appActions from 'app/redux/AppReducer'; @@ -338,11 +340,14 @@ function* broadcastPayload({ ); } } - if (successCallback) { try { + if (successCallback) { + try { successCallback(operations); } catch (error) { console.error('defaultErrorCallback', error); - } } + } + } + dispatcher.dispatch(EVENT_OPERATION_BROADCAST); } catch (error) { console.error('TransactionSaga\tbroadcastPayload', error); // status: error diff --git a/src/app/utils/UserUtil.js b/src/app/utils/UserUtil.js index a40d575e6cfbd213cff894af2c6948d70573cc1d..bc583e7a3a8b277be0750f884870f548be51ccfa 100644 --- a/src/app/utils/UserUtil.js +++ b/src/app/utils/UserUtil.js @@ -23,6 +23,27 @@ export const packLoginData = ( }\t${access_token || ''}\t${expires_in || ''}` ).toString('hex'); +export const calculateRcStats = (userRc) => { + const manaRegenerationTime = 432000; + const currentTime = parseInt((new Date().getTime() / 1000).toFixed(0)); + const stats = { + resourceCreditsPercent: 0, + resourceCreditsWaitTime: 0, + }; + + // Resource Credits + const maxRcMana = parseFloat(userRc.max_rc); + const rcManaElapsed = currentTime - parseInt(userRc.rc_manabar.last_update_time); + let currentRcMana = parseFloat(userRc.rc_manabar.current_mana) + (rcManaElapsed * maxRcMana) / manaRegenerationTime; + if (currentRcMana > maxRcMana) { + currentRcMana = maxRcMana; + } + stats.resourceCreditsPercent = Math.round((currentRcMana * 100) / maxRcMana); + stats.resourceCreditsWaitTime = ((100 - stats.resourceCreditsPercent) * manaRegenerationTime) / 100; + + return stats; +}; + /** * * @returns {array} [username, password, memoWif, login_owner_pubkey, login_with_keychain, @@ -33,4 +54,5 @@ export const extractLoginData = (data) => Buffer.from(data, 'hex').toString().sp export default { isLoggedIn, extractLoginData, + calculateRcStats, }; diff --git a/src/shared/constants.js b/src/shared/constants.js index 657e710666ce1293bc974822c6409dbc1092b501..582d3e7ed6e27e12343a600141d96b514cb50b99 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -3,3 +3,4 @@ export const VIEW_MODE_WHISTLE = 'whistle'; export const WHISTLE_SIGNUP_COMPLETE = 'whistle_signup_complete'; export const SIGNUP_URL = 'https://signup.hive.io'; export const SUBMIT_FORM_ID = 'submitStory'; +export const EVENT_OPERATION_BROADCAST = 'event_operation_broadcast'; diff --git a/yarn.lock b/yarn.lock index 5fcb319ddb96e97912e90f1531206099c6ede8e0..0bd0199b242c2b7be4f553aa08f573b89ab655d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9076,6 +9076,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +humanize-duration@^3.27.0: + version "3.27.0" + resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.27.0.tgz#3f781b7cf8022ad587f76b9839b60bc2b29636b2" + integrity sha512-qLo/08cNc3Tb0uD7jK0jAcU5cnqCM0n568918E7R2XhMr/+7F37p4EY062W/stg7tmzvknNn9b/1+UhVRzsYrQ== + humanize-number@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/humanize-number/-/humanize-number-0.0.2.tgz#11c0af6a471643633588588048f1799541489c18" @@ -13567,6 +13572,11 @@ react-dev-utils@^11.0.3: strip-ansi "6.0.0" text-table "0.2.0" +react-dispatch@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-dispatch/-/react-dispatch-1.2.0.tgz#d1baac37443e4d2e3136169aebd9fb74f3142633" + integrity sha512-yyWAG78PlUOO4FWNTWaEfNiFIyvjTiQPSy6SvRO+JvTurrLZdR7kPLwwnyq6ZbgtCdtOIGdfDyOjHLEIoLFLHA== + react-docgen-typescript@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.1.0.tgz#20db64a7fd62e63a8a9469fb4abd90600878cbb2" @@ -13893,6 +13903,13 @@ react-textarea-autosize@^8.3.0: use-composed-ref "^1.0.0" use-latest "^1.0.0" +react-tooltip-lite@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/react-tooltip-lite/-/react-tooltip-lite-1.12.0.tgz#f6cd1323cdd9f5f80dd0e71a30ef59f401dee9ba" + integrity sha512-QjDnmDmjtLNKvLY6bzUOG8W6ZDBTiE4UXugGzClOQEGvMvbkJn2GvZvLwRaxsN/GCx7589RgbGaESMiJAm+zWg== + dependencies: + prop-types "^15.5.8" + react-transition-group@^4.3.0: version "4.4.2" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"