added follow support

This commit is contained in:
srgooglo 2020-10-30 19:35:38 +01:00
parent 7f28f854b1
commit 57aedb84d4
8 changed files with 169 additions and 23 deletions

View File

@ -6,6 +6,7 @@ import verbosity from 'core/libs/verbosity'
export default class LikeBtn extends React.Component {
state = {
hoveringCounter: false,
liked: this.props.liked,
count: this.props.count,
clicked: false,
@ -34,21 +35,31 @@ export default class LikeBtn extends React.Component {
}
}, 3000))
} else {
return false
}
}
handleLeave() {
if (this.state.hoveringCounter) {
this.setState({hoveringCounter: false })
}
}
handleOver() {
if (!this.state.hoveringCounter) {
this.setState({hoveringCounter: true })
}
}
getDecoratorCount(count) {
return `${core.abbreviateCount(new Number(count).toString())}`
return <span>{this.state.hoveringCounter? `${count}` : core.abbreviateCount(new Number(count).toString())}</span>
}
render() {
const { liked, clicked, count } = this.state
return (
<div>
<div onMouseLeave={() => this.handleLeave()} onMouseOver={() => this.handleOver()}>
<button onClick={() => { this.handleClick() }} className={classnames(styles.like_button, { [styles.clickanim]: clicked })} >
<div className={styles.like_wrapper}>
<div
@ -71,7 +82,7 @@ export default class LikeBtn extends React.Component {
</svg>
</div>
</button>
<div className={styles.likesIndicator} >
<div className={classnames(styles.likesIndicator, {[styles.hover]: this.state.hoveringCounter})} >
<span className={classnames(styles.likeCounter, {
[styles.active]: !clicked,
[styles.past]: clicked,

View File

@ -16,6 +16,7 @@
}
.likesIndicator{
cursor: default;
margin: auto;
position: absolute;
z-index: 12;
@ -24,26 +25,32 @@
background-color: #fff;
border-radius: 0 12px 12px 0;
color: #333;
width: fit-content;
width: 52px;
height: fit-content;
transition: all 150ms ease-in-out;
transform: translate(30px, -4px);
padding: 5px 14px;
&.hover{
width: 90px;
}
text-align: center;
}
.likeCounter {
font-family: "Poppins", sans-serif;
opacity: 0;
transform: perspective(100px) translateZ(10px);
filter: blur(10px);
letter-spacing: 0.1em;
letter-spacing: 0;
&.active {
opacity: 1;
//transform: perspective(100px) translateZ(0px);
filter: blur(0px);
letter-spacing: 0.15em;
transition: opacity @blur-transition-duration linear, transform @blur-transition-duration linear, filter @blurFilter-transition-duration linear, letter-spacing @blur-transition-duration linear;
}
@ -51,7 +58,7 @@
opacity: 0;
//transform: perspective(100px) translateZ(-10px);
filter: blur(1px);
letter-spacing: 0.2em;
letter-spacing: 0.15em;
transition: opacity @blur-transition-duration linear, transform @blur-transition-duration linear, filter @blurFilter-transition-duration linear, letter-spacing @blur-transition-duration linear;
}
}

View File

@ -32,6 +32,8 @@
}
.ant-card {
cursor: default;
border-radius: @post_card_general_border-rd;
border: 0;
border-top: 1px solid #4646460c;

View File

@ -88,8 +88,8 @@ export default {
then(socket)
}
if (typeof(query) !== "undefined") {
socket._emit(invoke, query.payload, (callback) =>{
new Promise((resolve, reject) => resolve(query.callback(callback))).then(() => {
socket._emit(invoke, query.payload, (...callbacks) =>{
new Promise((resolve, reject) => resolve(query.callback(...callbacks)) ).then(() => {
if (!persistent) {
socket.remove()
}

View File

@ -16,6 +16,24 @@ export default {
state: {
},
effects: {
*actions({callback, payload}, { call, select }) {
dispatch({
type: "socket/use",
scope: "users",
invoke: "actions",
query: {
payload: {
from: payload.from,
user_id: payload.user_id ?? state.app.session_uuid,
username: payload.username ?? state.app.session_authframe["username"],
userToken: state.app.session_token
},
callback: (callbackResponse) => {
return callback(callbackResponse)
}
}
})
},
*get({ callback, payload }, { call, put, select }) {
const dispatch = yield select(state => state.app.dispatcher)
const state = yield select(state => state)

View File

@ -1,8 +1,10 @@
import React from 'react'
import { pathMatchRegexp } from 'core'
import { pathMatchRegexp, booleanFix, __legacy__objectToArray } from 'core'
import HandleError from 'core/libs/errorhandler'
import { Invalid } from 'components'
import styles from './index.less'
import GlobalBadges from 'globals/badges_list.json'
import * as Icons from 'components/Icons'
import FollowButton from './components/follow'
import Menu from './components/menu'
@ -10,8 +12,9 @@ import { PostsFeed } from 'components'
import * as antd from 'antd'
import { connect } from 'umi'
import { verbosity } from '../../core/libs'
class UserLayout extends React.Component {
export class UserLayout extends React.Component {
state = {
styleComponent: "UserLayout",
userString: pathMatchRegexp('/@/:id', location.pathname)[1],
@ -24,6 +27,22 @@ class UserLayout extends React.Component {
}
}
handleClickFollow(user_id) {
if (typeof (this.props.onFollow) !== "undefined") {
this.updateFollow(!booleanFix(this.state.layoutData.is_following))
this.props.onFollow(user_id, (callback) => {
this.updateFollow(callback)
})
}
}
updateFollow(to) {
let updated = this.state.layoutData
updated.is_following = to
this.setState({ layoutData: updated })
}
componentDidMount() {
const { layoutData } = this.props
if (layoutData) {
@ -31,11 +50,61 @@ class UserLayout extends React.Component {
}
}
renderUserBadges() {
let { layoutData } = this.state
if (typeof(layoutData.user_tags) == "undefined") {
return null
}
let userTags = __legacy__objectToArray(layoutData.user_tags)
let renderTags = []
if (!userTags) {
return null
}
try {
userTags = JSON.parse(userTags[0].value.badges)
} catch (error) {
console.log(error)
}
if (!userTags) {
return null
}
__legacy__objectToArray(GlobalBadges).forEach(e => {
if(userTags.includes(e.value.id)) {
renderTags.push(e.value)
}
})
try {
if (Array.isArray(userTags)) {
return renderTags.map((element) => {
return(
<antd.Tooltip key={element.key ?? Math.random()} title={element.tip} >
<antd.Tag icon={React.createElement(Icons[element.icon]) ?? null} key={element.key ?? Math.random()} color={element.color ?? "default"} >
{element.title ?? "maybe"}
</antd.Tag>
</antd.Tooltip>
)
})
}
} catch (error) {
return null
}
return null
}
render() {
const { styleComponent } = this.state
const toStyles = e => styles[`${styleComponent}_${e}`]
const { followers_count } = this.state.layoutData.details ?? { }
const { followers_count } = this.state.layoutData.details ?? {}
const isFollowed = booleanFix(this.state.layoutData.is_following)
if (!this.state.layoutData) {
return null
}
return (
<div className={toStyles("wrapper")} >
<div className={toStyles("cover")}>
@ -46,8 +115,10 @@ class UserLayout extends React.Component {
<div className={toStyles("avatar")}>
<antd.Avatar shape="square" src={this.state.layoutData.avatar} />
</div>
<div className={toStyles("title")}>
<div style={{ display: "flex", alignItems: "center", marginBottom: "7px" }} >
{/* {this.renderUserBadges()} */}
</div>
<antd.Tooltip title={`${followers_count ?? "Non-existent"} Followers`}>
<h1>{this.state.userString}</h1>
</antd.Tooltip>
@ -60,19 +131,19 @@ class UserLayout extends React.Component {
marginBottom: '5px',
}}
dangerouslySetInnerHTML={{
__html: this.state.layoutData.about,
__html: typeof(this.state.layoutData.about) == "string"? this.state.layoutData.about : null,
}}
/>
</div>
<div className={toStyles("options")}>
<div><FollowButton followed={this.state.layoutData.follow} /></div>
<div><FollowButton onClick={() => { this.handleClickFollow(this.state.layoutData.user_id) }} followed={isFollowed} /></div>
</div>
</div>
<div className={toStyles("content")}>
</div>
</div>
)
@ -90,6 +161,41 @@ export default class UserIndexer extends React.Component {
promiseState = async state => new Promise(resolve => this.setState(state, resolve));
handleClickFollow(user_id, callback) {
if (this.props.app.session_valid) {
const requestCallback = (callbackResponse) => {
if (callbackResponse.code == 200) {
const response = callbackResponse.response
const result =( response.follow ?? false) == "followed" ? true : false
if (typeof(response) !== "undefined") {
return callback(result)
}else{
return false
}
}else{
return callback(null)
}
}
this.props.dispatch({
type: "socket/use",
scope: "users",
invoke: "actions",
query: {
payload: {
userToken: this.props.app.session_token,
action: "follow",
user_id,
},
callback: requestCallback
}
})
}else{
verbosity(`Need auth`)
}
}
componentDidMount() {
const matchRegexp = pathMatchRegexp('/@/:id', location.pathname)
@ -123,7 +229,7 @@ export default class UserIndexer extends React.Component {
}
return (
<div>
<UserLayout layoutData={this.state.layoutData} />
<UserLayout onFollow={(...context) => {this.handleClickFollow(...context)}} layoutData={this.state.layoutData} />
<PostsFeed from="user" fromID={this.state.layoutData.user_id} />
</div>

View File

@ -4,7 +4,7 @@ import classnames from 'classnames'
const FollowButton = (props) => {
return (
<a className={classnames(styles.button, {[styles.disabled]: !props.followed })}>
<a onClick={props.onClick} className={classnames(styles.button, {[styles.disabled]: !props.followed })}>
<span>{props.followed ? 'Following' : 'Follow'}</span>
</a>
)

View File

@ -82,10 +82,12 @@
.ant-avatar {
box-shadow: 13px 13px 17px 4px rgba(69, 69, 69, 0.151);
border-radius: 7px;
height: 120px;
width: 120px;
img {
height: 120px;
width: 120px;
height: 100%;
width: 100%;
}
}
}