Migrate components to typescript
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node_modules/
|
||||
public/bundle.js
|
||||
config/
|
||||
.vscode/
|
||||
.vscode/
|
||||
src/data/awsClient
|
||||
src/components/AWS.tsx
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the circles which the specify users is in that circle
|
||||
* @param {object} circles
|
||||
* @param {string} followingId
|
||||
*/
|
||||
export const getUserBelongCircles = (circles,followingId) => {
|
||||
let userBelongCircles = []
|
||||
Object.keys(circles).forEach((cid) => {
|
||||
if(cid.trim() !== '-Followers' && circles[cid].users){
|
||||
let isExist = Object.keys(circles[cid].users).indexOf(followingId) > -1
|
||||
if(isExist){
|
||||
userBelongCircles.push(cid)
|
||||
}
|
||||
}
|
||||
})
|
||||
return userBelongCircles
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the following users
|
||||
* @param {object} circles
|
||||
*/
|
||||
export const getFollowingUsers = (circles) => {
|
||||
let followingUsers = {}
|
||||
Object.keys(circles).forEach((cid) => {
|
||||
if(cid.trim() !== '-Followers' && circles[cid].users){
|
||||
Object.keys(circles[cid].users).forEach((userId)=>{
|
||||
let isExist = Object.keys(followingUsers).indexOf(userId) > -1
|
||||
if(!isExist){
|
||||
followingUsers[userId] = {
|
||||
...circles[cid].users[userId]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
return followingUsers
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
getUserBelongCircles,
|
||||
getFollowingUsers
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// - Import react component
|
||||
import { storageRef } from 'app/firebaseClient/'
|
||||
|
||||
//- Import actions
|
||||
|
||||
// - Get file Extension
|
||||
const getExtension = (fileName) => {
|
||||
let re = /(?:\.([^.]+))?$/
|
||||
return re.exec(fileName)[1]
|
||||
}
|
||||
|
||||
// Converts image to canvas returns new canvas element
|
||||
const convertImageToCanvas = (image) => {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
canvas.getContext('2d').drawImage(image, 0, 0)
|
||||
|
||||
return canvas
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload image on the server
|
||||
* @param {file} file
|
||||
* @param {string} fileName
|
||||
*/
|
||||
const uploadImage = (file, fileName, progress) => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Create a storage refrence
|
||||
let storegeFile = storageRef.child(`images/${fileName}`)
|
||||
|
||||
// Upload file
|
||||
let task = storegeFile.put(file)
|
||||
task.then((result) => {
|
||||
resolve(result)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
// Upload storage bar
|
||||
task.on('state_changed', (snapshot) => {
|
||||
let percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
|
||||
progress(percentage, true)
|
||||
}, (error) => {
|
||||
console.log('========== Upload Image ============')
|
||||
console.log(error)
|
||||
console.log('====================================')
|
||||
|
||||
}, (complete) => {
|
||||
progress(100, false)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint image size
|
||||
* @param {file} file
|
||||
* @param {number} maxWidth
|
||||
* @param {number} maxHeight
|
||||
*/
|
||||
const constraintImage = (file,fileName, maxWidth, maxHeight) => {
|
||||
// Ensure it's an image
|
||||
if(file.type.match(/image.*/)) {
|
||||
|
||||
// Load the image
|
||||
let reader = new FileReader()
|
||||
reader.onload = function (readerEvent) {
|
||||
let image = new Image()
|
||||
image.onload = function (imageEvent) {
|
||||
|
||||
// Resize the image
|
||||
let canvas = document.createElement('canvas'),
|
||||
max_size = 986,// TODO : pull max size from a site config
|
||||
width = image.width,
|
||||
height = image.height
|
||||
if (width > height) {
|
||||
if (width > max_size) {
|
||||
height *= max_size / width
|
||||
width = max_size
|
||||
}
|
||||
} else {
|
||||
if (height > max_size) {
|
||||
width *= max_size / height
|
||||
height = max_size
|
||||
}
|
||||
}
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
|
||||
let dataUrl = canvas.toDataURL()
|
||||
let resizedImage = dataURLToBlob(dataUrl)
|
||||
let evt = new CustomEvent('onSendResizedImage', { detail: {resizedImage,fileName} })
|
||||
window.dispatchEvent(evt)
|
||||
|
||||
|
||||
}
|
||||
image.src = readerEvent.target.result
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert data URL to blob
|
||||
* @param {object} dataURL
|
||||
*/
|
||||
const dataURLToBlob = (dataURL) => {
|
||||
|
||||
let BASE64_MARKER = 'base64,'
|
||||
if (dataURL.indexOf(BASE64_MARKER) == -1) {
|
||||
let parts = dataURL.split(',')
|
||||
let contentType = parts[0].split(':')[1]
|
||||
let raw = parts[1]
|
||||
|
||||
return new Blob([raw], {type: contentType})
|
||||
}
|
||||
|
||||
let parts = dataURL.split(BASE64_MARKER)
|
||||
let contentType = parts[0].split(':')[1]
|
||||
let raw = window.atob(parts[1])
|
||||
let rawLength = raw.length
|
||||
|
||||
let uInt8Array = new Uint8Array(rawLength)
|
||||
|
||||
for (let i = 0 ;i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i)
|
||||
}
|
||||
|
||||
return new Blob([uInt8Array], {type: contentType})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
dataURLToBlob,
|
||||
convertImageToCanvas,
|
||||
getExtension,
|
||||
constraintImage,
|
||||
uploadImage
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
// Get tags from post content
|
||||
export const detectTags = (content,character) => {
|
||||
|
||||
return content.split(" ").filter((word) => {
|
||||
return (word.slice(0,1) === character)
|
||||
})
|
||||
|
||||
}
|
||||
export const getContentTags = (content) => {
|
||||
let newTags = []
|
||||
let tags = detectTags(content,'#')
|
||||
tags.forEach((tag)=>{
|
||||
newTags.push(tag.slice(1))
|
||||
})
|
||||
return newTags
|
||||
}
|
||||
|
||||
export const sortObjectsDate = (objects) => {
|
||||
let sortedObjects = objects
|
||||
|
||||
// Sort posts with creation date
|
||||
sortedObjects.sort((a, b) => {
|
||||
return parseInt(b.creationDate) - parseInt(a.creationDate)
|
||||
|
||||
})
|
||||
|
||||
return sortedObjects
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// - Import react component
|
||||
import { storageRef } from 'app/firebaseClient/'
|
||||
|
||||
//- Import actions
|
||||
|
||||
const isValidEmail = (email) => {
|
||||
let re = /^(([^<>()\[\]\\.,:\s@"]+(\.[^<>()\[\]\\.,:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
return re.test(email)
|
||||
}
|
||||
|
||||
function queryString(name, url = window.location.href) {
|
||||
name = name.replace(/[[]]/g, "\\$&");
|
||||
|
||||
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)", "i");
|
||||
const results = regex.exec(url);
|
||||
|
||||
if (!results) {
|
||||
return null;
|
||||
}
|
||||
if (!results[2]) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
export default {
|
||||
isValidEmail,
|
||||
queryString
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import {push} from 'react-router-redux'
|
||||
import {grey400, darkBlack, lightBlack} from 'material-ui/styles/colors'
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
import SvgGroup from 'material-ui/svg-icons/action/group-work'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
|
||||
import IconMenu from 'material-ui/IconMenu'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import IconButtonElement from 'IconButtonElement'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import SvgClose from 'material-ui/svg-icons/navigation/close'
|
||||
import AppBar from 'material-ui/AppBar'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'UserAvatar'
|
||||
|
||||
|
||||
// - Import API
|
||||
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'circleActions'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class Circle extends Component {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Circle object
|
||||
*/
|
||||
circle: PropTypes.object.isRequired,
|
||||
/**
|
||||
* Circle identifier
|
||||
*/
|
||||
id: PropTypes.string.isRequired
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* If is true circle is open to show users in circle list
|
||||
*/
|
||||
open:false,
|
||||
/**
|
||||
* Circle name on change
|
||||
*/
|
||||
circleName: this.props.circle.name,
|
||||
/**
|
||||
* Save operation will be disable if user doesn't meet requirement
|
||||
*/
|
||||
disabledSave: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleToggleCircle = this.handleToggleCircle.bind(this)
|
||||
this.handleDeleteCircle = this.handleDeleteCircle.bind(this)
|
||||
this.handleUpdateCircle = this.handleUpdateCircle.bind(this)
|
||||
this.handleChangeCircleName = this.handleChangeCircleName.bind(this)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle chage circle name
|
||||
*
|
||||
*
|
||||
* @memberof Circle
|
||||
*/
|
||||
handleChangeCircleName = (evt) => {
|
||||
const {value} = evt.target
|
||||
this.setState({
|
||||
circleName: value,
|
||||
disabledSave: (!value || value.trim() === '')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's circle
|
||||
*
|
||||
*
|
||||
* @memberof Circle
|
||||
*/
|
||||
handleUpdateCircle = () => {
|
||||
const {circleName} = this.state
|
||||
if(circleName && circleName.trim() !== ''){
|
||||
this.props.updateCircle({name:circleName,id: this.props.id})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete circle
|
||||
*
|
||||
*
|
||||
* @memberof Circle
|
||||
*/
|
||||
handleDeleteCircle = () => {
|
||||
this.props.deleteCircle(this.props.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle circle to close/open
|
||||
*
|
||||
*
|
||||
* @memberof Circle
|
||||
*/
|
||||
handleToggleCircle = () => {
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
userList = () => {
|
||||
const {users} = this.props.circle
|
||||
const {userInfo} = this.props
|
||||
let usersParsed =[]
|
||||
|
||||
if(users){
|
||||
Object.keys(users).forEach((key, index) => {
|
||||
const { fullName} = users[key]
|
||||
let avatar = userInfo && userInfo[key] ? userInfo[key].avatar || '' : ''
|
||||
usersParsed.push(<ListItem
|
||||
key={`${this.props.id}.${key}`}
|
||||
style={{backgroundColor: '#e2e2e2'}}
|
||||
value={2}
|
||||
primaryText={fullName}
|
||||
leftAvatar={<UserAvatar fullName={fullName} fileName={avatar}/>}
|
||||
onClick={()=> this.props.goTo(`/${key}`)}
|
||||
/>)
|
||||
|
||||
})
|
||||
return usersParsed
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Right icon menue of circle
|
||||
*
|
||||
*
|
||||
* @memberof Circle
|
||||
*/
|
||||
rightIconMenu =(
|
||||
<IconMenu iconButtonElement={IconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "12px" }}>
|
||||
<MenuItem primaryText="Delete circle" onClick={this.handleDeleteCircle} />
|
||||
<MenuItem primaryText="Circle settings" onClick={this.props.openCircleSettings} />
|
||||
</IconMenu>
|
||||
)
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
|
||||
const circleTitle =(
|
||||
<div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: "space-between" }}>
|
||||
<div style={{paddingRight: '10px'}}>
|
||||
<SvgClose onClick={this.props.closeCircleSettings} hoverColor={grey400} style={{cursor: 'pointer'}} />
|
||||
</div>
|
||||
<div style={{
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
flex: '1 1',
|
||||
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif'
|
||||
}}>
|
||||
Circle settings
|
||||
</div>
|
||||
<div style={{marginTop: '-9px'}}>
|
||||
<FlatButton label="SAVE" primary={true} disabled={this.state.disabledSave} onClick={this.handleUpdateCircle} />
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<ListItem
|
||||
key={this.props.id}
|
||||
style={{backgroundColor: '#fff', borderBottom: '1px solid rgba(0,0,0,0.12)',height: '72px',padding: '12px 0' }}
|
||||
primaryText={<span style={{color:'rgba(0,0,0,0.87)',fontSize:'16px',marginRight:'8px',whiteSpace:'nowrap',textOverflow:'ellipsis',overflow:'hidden'}}>{this.props.circle.name}</span>}
|
||||
leftIcon={<SvgGroup style={{width:'40px',height:'40px',transform: 'translate(0px, -9px)',fill:'#bdbdbd'}} />}
|
||||
rightIconButton={this.rightIconMenu}
|
||||
initiallyOpen={false}
|
||||
onClick={this.handleToggleCircle}
|
||||
open={this.state.open}
|
||||
nestedItems={this.userList()}
|
||||
>
|
||||
<Dialog
|
||||
id={this.props.id}
|
||||
title={circleTitle}
|
||||
modal={false}
|
||||
open={this.props.openSetting}
|
||||
onRequestClose={this.props.closeCircleSettings}
|
||||
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
|
||||
contentStyle={{maxWidth: '400px'}}
|
||||
>
|
||||
<div>
|
||||
<TextField
|
||||
hintText="Circle name"
|
||||
floatingLabelText="Circle name"
|
||||
onChange={this.handleChangeCircleName}
|
||||
value={this.state.circleName}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</ListItem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
let {uid} = ownProps
|
||||
return {
|
||||
deleteCircle: (id) => dispatch(circleActions.dbDeleteCircle(id)),
|
||||
updateCircle: (circle) => dispatch(circleActions.dbUpdateCircle(circle)),
|
||||
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid,ownProps.id)),
|
||||
openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid,ownProps.id)),
|
||||
goTo: (url)=> dispatch(push(url))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
let {uid} = state.authorize
|
||||
return {
|
||||
openSetting: state.circle ? (state.circle.userCircles[uid] ? (state.circle.userCircles[uid][ownProps.id].openCircleSettings || false) : false ) : false,
|
||||
userInfo: state.user.info
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Circle)
|
||||
@@ -1,84 +0,0 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import Faker from 'faker'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Import actions
|
||||
import * as commentActions from 'commentActions'
|
||||
|
||||
// - Define variable
|
||||
const buttonStyle = {
|
||||
marginTop: '5px'
|
||||
}
|
||||
|
||||
// - Create CommentWrite component class
|
||||
export class CommentWrite extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
inputValue:''
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleRef = this.handleRef.bind(this)
|
||||
this.focus = this.focus.bind(this)
|
||||
this.handleAddComment = this.handleAddComment.bind(this)
|
||||
this.handleOnChange = this.handleOnChange.bind(this)
|
||||
|
||||
}
|
||||
|
||||
handleOnChange = (evt) =>{
|
||||
this.setState({inputValue:evt.target.value})
|
||||
|
||||
}
|
||||
|
||||
handleRef = c => {
|
||||
this.inputRef = c
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
this.inputRef.focus()
|
||||
}
|
||||
handleAddComment = (evt) => {
|
||||
this.props.send(this.state.inputValue,this.props.postId,this.props.close)
|
||||
|
||||
}
|
||||
|
||||
// Render DOM
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<textarea autoFocus defaultValue={this.props.commentText} onChange={this.handleOnChange}/>
|
||||
|
||||
<Button basic style={buttonStyle} onClick={this.handleAddComment} color='teal'>Add Comment</Button>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// - Map dispatch to props
|
||||
const mapDispatchToProps = (dispatch,ownProps) => {
|
||||
return{
|
||||
send: (text,postId,callBack) => {
|
||||
dispatch(commentActions.dbAddComment(null,{
|
||||
postId: postId,
|
||||
text: text
|
||||
},callBack))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - Map state to props
|
||||
const mapStateToProps = (state) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(CommentWrite)
|
||||
@@ -1,248 +0,0 @@
|
||||
// - Impoer react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { GridList, GridTile } from 'material-ui/GridList'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import Subheader from 'material-ui/Subheader'
|
||||
import StarBorder from 'material-ui/svg-icons/toggle/star-border'
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton'
|
||||
import SvgUpload from 'material-ui/svg-icons/file/cloud-upload'
|
||||
import SvgAddImage from 'material-ui/svg-icons/image/add-a-photo'
|
||||
import SvgDelete from 'material-ui/svg-icons/action/delete'
|
||||
import { grey200, grey600 } from 'material-ui/styles/colors'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import uuid from 'uuid'
|
||||
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'imageGalleryActions'
|
||||
import * as globalActions from 'globalActions'
|
||||
|
||||
// - Import app components
|
||||
import Img from 'Img'
|
||||
|
||||
// - Import API
|
||||
import FileAPI from 'FileAPI'
|
||||
|
||||
/**
|
||||
* Create ImageGallery component class
|
||||
*/
|
||||
export class ImageGallery extends Component {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Callback function to ser image url on parent component
|
||||
*/
|
||||
open: PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// Binding function to `this`
|
||||
this.close = this.close.bind(this)
|
||||
this.onFileChange = this.onFileChange.bind(this)
|
||||
this.handleSetImage = this.handleSetImage.bind(this)
|
||||
this.handleDeleteImage = this.handleDeleteImage.bind(this)
|
||||
this.imageList = this.imageList.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle set image
|
||||
* @param {event} evt passed by on click event on add image
|
||||
* @param {string} name is the name of the image
|
||||
*/
|
||||
handleSetImage = (evt, URL,fullPath) => {
|
||||
this.props.set(URL,fullPath)
|
||||
this.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete image
|
||||
* @param {event} evt passed by on click event on delete image
|
||||
* @param {integer} id is the image identifier which selected to delete
|
||||
*/
|
||||
handleDeleteImage = (evt, id) => {
|
||||
this.props.deleteImage(id)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("onSendResizedImage", this.handleSendResizedImage)
|
||||
}
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("onSendResizedImage", this.handleSendResizedImage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle send image resize event that pass the resized image
|
||||
*
|
||||
*
|
||||
* @memberof ImageGallery
|
||||
*/
|
||||
handleSendResizedImage = (event) => {
|
||||
|
||||
const { resizedImage, fileName } = event.detail
|
||||
const {saveImageGallery, progressChange} = this.props
|
||||
|
||||
FileAPI.uploadImage(resizedImage, fileName, (percent, status) => {
|
||||
progressChange(percent,status)
|
||||
}).then((result) => {
|
||||
|
||||
/* Add image to image gallery */
|
||||
saveImageGallery(result.downloadURL,result.metadata.fullPath)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle on change file upload
|
||||
*/
|
||||
onFileChange = (evt) => {
|
||||
|
||||
const extension = FileAPI.getExtension(evt.target.files[0].name)
|
||||
let fileName = (`${uuid()}.${extension}`)
|
||||
let image = FileAPI.constraintImage(evt.target.files[0], fileName)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide image gallery
|
||||
*/
|
||||
close = () => {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
imageList = () => {
|
||||
|
||||
return this.props.images.map((image, index) => {
|
||||
|
||||
return (<GridTile
|
||||
key={image.id}
|
||||
title={<SvgDelete hoverColor={grey200} color="white" color="white" style={{ marginLeft: "5px", cursor: "pointer" }} onClick={evt => this.handleDeleteImage(evt, image.id)} />}
|
||||
subtitle={<span></span>}
|
||||
actionIcon={<SvgAddImage hoverColor={grey200} color="white" style={{ marginRight: "5px", cursor: "pointer" }} onClick={evt => this.handleSetImage(evt, image.URL,image.fullPath)} />}
|
||||
>
|
||||
<div>
|
||||
<div style={{ overflowY: "hidden", overflowX: "auto" }}>
|
||||
<ul style={{ whiteSpace: "nowrap", padding: "0 6px", margin: "8px 0 0 0", verticalAlign: "bottom", flexShrink: 0, listStyleType: "none" }}>
|
||||
<div style={{ display: "block" }}>
|
||||
<div style={{ display: "block", marginRight: "8px", transition: "transform .25s" }}>
|
||||
<li style={{ width: "100%", margin: 0, verticalAlign: "bottom", position: "static", display: "inline-block" }}>
|
||||
<Img fileName={image.URL} style={{ width: "100%", height: "auto" }} />
|
||||
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</GridTile>)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When the post text changed
|
||||
* @param {event} evt is an event passed by change post text callback funciton
|
||||
* @param {string} data is the post content which user writes
|
||||
*/
|
||||
render() {
|
||||
|
||||
|
||||
/**
|
||||
* Component styles
|
||||
* @type {Object}
|
||||
*/
|
||||
const styles = {
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
gridList: {
|
||||
width: 500,
|
||||
height: 450,
|
||||
overflowY: 'auto',
|
||||
},
|
||||
uploadButton: {
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
uploadInput: {
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
opacity: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.root}>
|
||||
<GridList
|
||||
cellHeight={180}
|
||||
style={styles.gridList}
|
||||
>
|
||||
<GridTile >
|
||||
|
||||
<div style={{ display: "flex", backgroundColor: "rgba(222, 222, 222, 0.52)", flexDirection: "column", justifyContent: "center", alignItems: "center", height: "100%" }}>
|
||||
|
||||
<FlatButton
|
||||
label="Upload Photo"
|
||||
labelStyle={{ fontWeight: 100 }}
|
||||
labelPosition="before"
|
||||
style={styles.uploadButton}
|
||||
containerElement="label"
|
||||
>
|
||||
<input type="file" onChange={this.onFileChange} accept="image/*" style={styles.uploadInput} />
|
||||
</FlatButton>
|
||||
</div>
|
||||
</GridTile>
|
||||
{this.imageList()}
|
||||
</GridList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
saveImageGallery: (imageURL,imageFullPath) => dispatch(imageGalleryActions.dbSaveImage(imageURL,imageFullPath)),
|
||||
deleteImage: (id) => dispatch(imageGalleryActions.dbDeleteImage(id)),
|
||||
progressChange : (percent,status) => dispatch(globalActions.progressChange(percent, status))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
images: state.imageGallery.images,
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : ''
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImageGallery)
|
||||
@@ -1,2 +0,0 @@
|
||||
import Master from './Master'
|
||||
export default Master
|
||||
@@ -1,55 +0,0 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
import CircularProgress from 'material-ui/CircularProgress'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Create MasterLoading component class
|
||||
export default class MasterLoading extends Component {
|
||||
|
||||
|
||||
|
||||
// Constructor
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Render app DOM component
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
modal={true}
|
||||
open={this.props.activeLoading}
|
||||
autoDetectWindowHeight={false}
|
||||
overlayStyle={{backgroundColor: "white"}}
|
||||
contentClassName="mLoading__content"
|
||||
bodyStyle={{backgroundColor: ""}}
|
||||
bodyClassName="mLoading__body"
|
||||
>
|
||||
|
||||
<div>
|
||||
<div className="mLoading__context">
|
||||
|
||||
<CircularProgress color="white" size={80} thickness={7} />
|
||||
<h1 style={{float:"right", color:"#fff"}}>Green</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</Dialog>
|
||||
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
|
||||
// - Import components
|
||||
|
||||
// - Import actions
|
||||
|
||||
// - Create component class
|
||||
export default class SidebarContent extends Component {
|
||||
|
||||
static qcName= 'SidebarContent'
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
this.state = {}
|
||||
|
||||
// Binding function to `this`
|
||||
this.handleClickCover = this.handleClickCover.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click on cover of sidebar
|
||||
* @param {event} evt is a click event passed to funciton
|
||||
*/
|
||||
handleClickCover = (evt) => {
|
||||
this.props.sidebar(false,'overlay')
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
let showCoverStyle = {position: "fixed", height: "100%", width: "100%", top: "0px", left: "0px", opacity: "1", backgroundColor: "rgba(255, 255, 255, 0.54)", WebkitTapHighlightColor: "rgba(0, 0, 0, 0)", willChange: "opacity", transform: "translateZ(0px)", transition: "left 0ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms", zIndex: "1111", pointerEvents: "auto"}
|
||||
let hideCoverStyle = {position: "fixed", height: "100%", width: "100%", top: "0px", left: "-100%", opacity: "0", backgroundColor: "rgba(255, 255, 255, 0.54)", WebkitTapHighlightColor: "rgba(0, 0, 0, 0)", willChange: "opacity", transform: "translateZ(0px)", transition: "left 0ms cubic-bezier(0.23, 1, 0.32, 1) 400ms, opacity 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms", zIndex: "1111", pointerEvents: "none"}
|
||||
return (
|
||||
<div id='sidebar-content'>
|
||||
<div style={this.props.overlay ? showCoverStyle : hideCoverStyle} style={{overflow:'hidden'}} onClick={this.handleClickCover}></div>
|
||||
<div className={this.props.className} style={this.props.cstyle}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
|
||||
// - Import components
|
||||
|
||||
// - Import actions
|
||||
|
||||
// - Create component class
|
||||
export default class SidebarMain extends Component {
|
||||
|
||||
static qcName = 'SidebarMain'
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<div className="home__main" style={this.props.cstyle} >
|
||||
<div style={{height:"64px", width:"100%"}}></div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
// - Import react components
|
||||
import React,{Component} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {push} from 'react-router-redux'
|
||||
import {NavLink, withRouter} from 'react-router-dom'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
|
||||
// - Import actions
|
||||
import * as authorizeActions from 'authorizeActions'
|
||||
import * as globalActions from 'globalActions'
|
||||
|
||||
// - Import app API
|
||||
import StringAPI from 'StringAPI'
|
||||
|
||||
|
||||
// - Create Signup componet class
|
||||
export class Signup extends Component {
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props){
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
fullNameInput: '',
|
||||
fullNameInputError: '',
|
||||
emailInput: '',
|
||||
emailInputError: '',
|
||||
passwordInput: '',
|
||||
passwordInputError: '',
|
||||
confirmInput: '',
|
||||
confirmInputError: ''
|
||||
|
||||
}
|
||||
// Binding function to `this`
|
||||
this.handleForm = this.handleForm.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data on input change
|
||||
* @param {event} evt is an event of inputs of element on change
|
||||
*/
|
||||
handleInputChange = (evt) => {
|
||||
const target = evt.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
[name]: value
|
||||
})
|
||||
|
||||
|
||||
switch (name) {
|
||||
case 'fullNameInput':
|
||||
this.setState({
|
||||
fullNameInputError: '',
|
||||
})
|
||||
break
|
||||
case 'emailInput':
|
||||
this.setState({
|
||||
emailInputError: ''
|
||||
})
|
||||
break
|
||||
case 'passwordInput':
|
||||
this.setState({
|
||||
confirmInputError: '',
|
||||
passwordInputError: ''
|
||||
})
|
||||
break
|
||||
case 'confirmInput':
|
||||
this.setState({
|
||||
confirmInputError: '',
|
||||
passwordInputError: ''
|
||||
})
|
||||
break
|
||||
case 'checkInput':
|
||||
this.setState({
|
||||
checkInputError: ''
|
||||
})
|
||||
break
|
||||
default:
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle register form
|
||||
*/
|
||||
handleForm = () => {
|
||||
|
||||
const {fullNameInput, emailInput, passwordInput, confirmInput} = this.state
|
||||
const {register} = this.props
|
||||
|
||||
let error = false
|
||||
|
||||
// Validate full name
|
||||
let fullNameCheck = fullNameInput.trim().toLowerCase()
|
||||
|
||||
if (fullNameCheck.indexOf('test') > -1
|
||||
|| fullNameCheck.indexOf('demo') > -1
|
||||
|| fullNameCheck.indexOf('asd') > -1
|
||||
|| fullNameCheck.length < 4) {
|
||||
this.setState({
|
||||
fullNameInputError: 'Please enter a valid name.'
|
||||
})
|
||||
error = true
|
||||
}
|
||||
|
||||
/* Validate email*/
|
||||
if (!StringAPI.isValidEmail(emailInput)) {
|
||||
this.setState({
|
||||
emailInputError: 'Please enter a valid email.'
|
||||
})
|
||||
error = true
|
||||
|
||||
}
|
||||
|
||||
/* Check password */
|
||||
if (passwordInput === '') {
|
||||
this.setState({
|
||||
passwordInputError: 'This field is required.'
|
||||
})
|
||||
error = true
|
||||
|
||||
}
|
||||
if (confirmInput === '') {
|
||||
this.setState({
|
||||
confirmInputError: 'This field is required.'
|
||||
})
|
||||
error = true
|
||||
|
||||
}
|
||||
else if(confirmInput !== passwordInput){
|
||||
this.setState({
|
||||
passwordInputError: 'This field sould be equal to confirm password.',
|
||||
confirmInputError: 'This field sould be equal to password.'
|
||||
})
|
||||
error = true
|
||||
|
||||
}
|
||||
if (!error) {
|
||||
register({
|
||||
email: emailInput,
|
||||
password: passwordInput,
|
||||
fullName: fullNameInput
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
const paperStyle = {
|
||||
minHeight: 500,
|
||||
width: 450,
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
margin: "auto"
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
|
||||
<h1 style={{
|
||||
textAlign: "center",
|
||||
padding:"20px",
|
||||
fontSize: "30px",
|
||||
fontWeight: 500,
|
||||
lineHeight: "32px",
|
||||
margin: "auto",
|
||||
color: "rgba(138, 148, 138, 0.2)"
|
||||
}}>Green</h1>
|
||||
|
||||
<div className="animate-bottom">
|
||||
<Paper style={paperStyle} zDepth={1} rounded={false} >
|
||||
<div style={{padding: "48px 40px 36px"}}>
|
||||
<div style={{paddingLeft: "40px",
|
||||
paddingRight: "40px"}}>
|
||||
|
||||
<h2 style={{
|
||||
textAlign: "left",
|
||||
paddingTop: "16px",
|
||||
fontSize: "24px",
|
||||
fontWeight: 400,
|
||||
lineHeight: "32px",
|
||||
margin: 0}}>Sign up</h2>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.fullNameInputError}
|
||||
name="fullNameInput"
|
||||
floatingLabelStyle={{fontSize:"15px"}}
|
||||
floatingLabelText="Full Name"
|
||||
type="text"
|
||||
/><br />
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.emailInputError}
|
||||
name="emailInput"
|
||||
floatingLabelStyle={{fontSize:"15px"}}
|
||||
floatingLabelText="Email"
|
||||
type="email"
|
||||
/><br />
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.passwordInputError }
|
||||
name="passwordInput"
|
||||
floatingLabelStyle={{fontSize:"15px"}}
|
||||
floatingLabelText="Password"
|
||||
type="password"
|
||||
/><br />
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.confirmInputError }
|
||||
name="confirmInput"
|
||||
floatingLabelStyle={{fontSize:"15px"}}
|
||||
floatingLabelText="Confirm Password"
|
||||
type="password"
|
||||
/><br />
|
||||
<br />
|
||||
<div className="signup__button-box">
|
||||
<div>
|
||||
<FlatButton label="Login" onClick={this.props.loginPage} />
|
||||
</div>
|
||||
<div>
|
||||
<RaisedButton label="Create" primary={true} onClick={this.handleForm}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch,ownProps) => {
|
||||
return {
|
||||
showError: (message) => {
|
||||
dispatch(action(types.SHOW_ERROR_MESSAGE_GLOBAL)(error.message))
|
||||
},
|
||||
register: (data) => {
|
||||
dispatch(authorizeActions.dbSignup(data))
|
||||
},
|
||||
loginPage: () => {
|
||||
dispatch(push("/login"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state,ownProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Signup))
|
||||
@@ -1,295 +0,0 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import {push} from 'react-router-redux'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'
|
||||
import Menu from 'material-ui/Menu'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import Checkbox from 'material-ui/Checkbox'
|
||||
import TextField from 'material-ui/TextField'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'UserAvatar'
|
||||
|
||||
|
||||
// - Import API
|
||||
import CircleAPI from 'CircleAPI'
|
||||
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'circleActions'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class UserBox extends Component {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* User identifier
|
||||
*/
|
||||
userId: PropTypes.string,
|
||||
/**
|
||||
* User information
|
||||
*/
|
||||
user:PropTypes.object
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* It will be true if user follow popover is open
|
||||
*/
|
||||
open: false,
|
||||
/**
|
||||
* The value of circle input
|
||||
*/
|
||||
circleName: '',
|
||||
/**
|
||||
* It will be true if the text field for adding group is empty
|
||||
*/
|
||||
disabledAddCircle: true
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleChangeName = this.handleChangeName.bind(this)
|
||||
this.handleCreateCricle = this.handleCreateCricle.bind(this)
|
||||
this.handleFollowUser = this.handleFollowUser.bind(this)
|
||||
|
||||
}
|
||||
|
||||
handleFollowUser = (checked,cid) => {
|
||||
const {userId,user} = this.props
|
||||
const {avatar,fullName} = user
|
||||
if (checked) {
|
||||
this.props.addFollowingUser(cid,{avatar,userId,fullName})
|
||||
} else {
|
||||
this.props.deleteFollowingUser(cid,userId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle create circle
|
||||
*
|
||||
*
|
||||
* @memberof UserBox
|
||||
*/
|
||||
handleCreateCricle = () => {
|
||||
const {circleName} = this.state
|
||||
if(circleName && circleName.trim() !== '' ){
|
||||
this.props.createCircle(this.state.circleName)
|
||||
this.setState({
|
||||
circleName: '',
|
||||
disabledAddCircle: true
|
||||
})}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle change group name input to the state
|
||||
*
|
||||
*
|
||||
* @memberof UserBox
|
||||
*/
|
||||
handleChangeName = (evt) => {
|
||||
this.setState({
|
||||
circleName: evt.target.value,
|
||||
disabledAddCircle: (evt.target.value === undefined || evt.target.value.trim() === '')
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle touch tab on follow popover
|
||||
*
|
||||
*
|
||||
* @memberof UserBox
|
||||
*/
|
||||
handleTouchTap = (evt) => {
|
||||
// This prevents ghost click.
|
||||
evt.preventDefault()
|
||||
|
||||
this.setState({
|
||||
open: true,
|
||||
anchorEl: evt.currentTarget,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle close follow popover
|
||||
*
|
||||
*
|
||||
* @memberof UserBox
|
||||
*/
|
||||
handleRequestClose = () => {
|
||||
this.setState({
|
||||
open: false,
|
||||
})
|
||||
}
|
||||
|
||||
circleList = () => {
|
||||
let { circles, userId, userBelongCircles } = this.props
|
||||
|
||||
if (circles) {
|
||||
|
||||
return Object.keys(circles).map((key, index) => {
|
||||
if(key.trim() !== '-Followers'){
|
||||
let isBelong = userBelongCircles.indexOf(key) > -1
|
||||
|
||||
return <Checkbox
|
||||
key={key}
|
||||
style={{ padding: '10px' }}
|
||||
label={circles[key].name}
|
||||
labelStyle={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
width: '100%'
|
||||
}}
|
||||
onCheck={(evt,checked) => this.handleFollowUser(checked,key)}
|
||||
checked={isBelong}
|
||||
/>}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
|
||||
const styles = {
|
||||
paper: {
|
||||
height: 254,
|
||||
width: 243,
|
||||
margin: 10,
|
||||
textAlign: 'center',
|
||||
maxWidth: '257px'
|
||||
},
|
||||
followButton: {
|
||||
position: 'absolute',
|
||||
bottom: '8px',
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper style={styles.paper} zDepth={1} className='grid-cell'>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
padding: '30px'
|
||||
|
||||
}}>
|
||||
<div onClick={() => this.props.goTo(`/${this.props.userId}`)} style={{cursor:'pointer'}}>
|
||||
<UserAvatar
|
||||
fullName={this.props.fullName}
|
||||
fileName={this.props.avatar}
|
||||
size={90}
|
||||
/>
|
||||
</div>
|
||||
<div onClick={() => this.props.goTo(`/${this.props.userId}`)} className='people__name' style={{cursor:'pointer'}}>
|
||||
<div>
|
||||
{this.props.user.fullName}
|
||||
</div>
|
||||
</div>
|
||||
<div style={styles.followButton}>
|
||||
<FlatButton
|
||||
label={(this.props.belongCirclesCount && this.props.belongCirclesCount < 1) ? 'Follow'
|
||||
: (this.props.belongCirclesCount > 1 ? `${this.props.belongCirclesCount} Circles` : ((this.props.firstBelongCircle) ? this.props.firstBelongCircle.name : 'Follow'))}
|
||||
primary={true}
|
||||
onTouchTap={this.handleTouchTap}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Popover
|
||||
open={this.state.open}
|
||||
anchorEl={this.state.anchorEl}
|
||||
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
|
||||
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
|
||||
onRequestClose={this.handleRequestClose}
|
||||
animation={PopoverAnimationVertical}
|
||||
>
|
||||
<Menu >
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
maxHeight: '220px'
|
||||
}}>
|
||||
<div style={{ overflowY: 'auto', height: '100%' }}>
|
||||
{this.circleList()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '10px' }}>
|
||||
<TextField
|
||||
hintText="Group name"
|
||||
onChange={this.handleChangeName}
|
||||
value={this.state.circleName}
|
||||
/><br />
|
||||
<FlatButton label="ADD" primary={true} disabled={this.state.disabledAddCircle} onClick={this.handleCreateCricle} />
|
||||
</div>
|
||||
</Menu>
|
||||
</Popover>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
createCircle: (name) => dispatch(circleActions.dbAddCircle(name)),
|
||||
addFollowingUser: (cid,user) => dispatch(circleActions.dbAddFollowingUser(cid,user)),
|
||||
deleteFollowingUser: (cid,followingId) => dispatch(circleActions.dbDeleteFollowingUser(cid,followingId)),
|
||||
goTo: (url)=> dispatch(push(url))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { uid } = state.authorize
|
||||
const circles = state.circle ? (state.circle.userCircles[uid]|| {}) : {}
|
||||
const userBelongCircles = CircleAPI.getUserBelongCircles(circles,ownProps.userId)
|
||||
|
||||
return {
|
||||
circles: circles,
|
||||
userBelongCircles: userBelongCircles || [],
|
||||
belongCirclesCount: userBelongCircles.length || 0,
|
||||
firstBelongCircle: userBelongCircles ? (circles ? circles[userBelongCircles[0]] : {}) : {},
|
||||
avatar: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].avatar || '' : '',
|
||||
fullName: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].fullName || '' : ''
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserBox)
|
||||
@@ -1,63 +0,0 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
|
||||
|
||||
// - Import components
|
||||
|
||||
// - Import actions
|
||||
|
||||
// - Create component class
|
||||
export default class {component name} extends Component {
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props){
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render(){
|
||||
return(
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch,ownProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state,ownProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(Sidebar)
|
||||
@@ -1,72 +0,0 @@
|
||||
// - Import react components
|
||||
import { firebaseRef, firebaseAuth } from 'app/firebaseClient/'
|
||||
|
||||
import { SocialError } from 'domain/common'
|
||||
import { Notification } from 'domain/notifications'
|
||||
import { INotificationService } from 'services/notifications'
|
||||
|
||||
/**
|
||||
* Firbase notification service
|
||||
*
|
||||
* @export
|
||||
* @class NotificationService
|
||||
* @implements {INotificationService}
|
||||
*/
|
||||
export class NotificationService implements INotificationService {
|
||||
|
||||
public addNotification: (notification: Notification)
|
||||
=> Promise<void> = (notification: Notification) => {
|
||||
return new Promise<void>((resolve,reject) => {
|
||||
firebaseRef.child(`userNotifies/${notification.notifyRecieverUserId}`)
|
||||
.push(notification)
|
||||
.then(() => {
|
||||
resolve()
|
||||
})
|
||||
.catch((error: any) => {
|
||||
reject(new SocialError(error.code, error.message))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public getNotifications: (userId: string)
|
||||
=> Promise<{ [notifyId: string]: Notification }> = (userId) => {
|
||||
return new Promise<{ [notifyId: string]: Notification }>((resolve,reject) => {
|
||||
let notifiesRef: any = firebaseRef.child(`userNotifies/${userId}`)
|
||||
notifiesRef.on('value', (snapshot: any) => {
|
||||
let notifies: {[notifyId: string]: Notification} = snapshot.val() || {}
|
||||
resolve(notifies)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public deleteNotification: (notificationId: string, userId: string)
|
||||
=> Promise <void> = (notificationId, userId) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let updates: any = {}
|
||||
updates[`userNotifies/${userId}/${notificationId}`] = null
|
||||
firebaseRef.update(updates)
|
||||
.then(() => {
|
||||
resolve()
|
||||
})
|
||||
.catch((error: any) => {
|
||||
reject(new SocialError(error.code, error.message))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public setSeenNotification: (notificationId: string, userId: string, notification: Notification)
|
||||
=> Promise <void> = (notificationId, userId, notification) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let updates: any = {}
|
||||
updates[`userNotifies/${userId}/${notificationId}`] = notification
|
||||
firebaseRef.update(updates)
|
||||
.then(() => {
|
||||
resolve()
|
||||
})
|
||||
.catch((error: any) => {
|
||||
reject(new SocialError(error.code, error.message))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
|
||||
/**
|
||||
* Global state
|
||||
*
|
||||
* @export
|
||||
* @class GlobalState
|
||||
*/
|
||||
export class GlobalState {
|
||||
|
||||
/**
|
||||
* Set percent of loading progress and visibility for Master component
|
||||
*
|
||||
* @type {{
|
||||
* percent: number,
|
||||
* visible: Boolean
|
||||
* }}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
progress: {
|
||||
percent: number
|
||||
visible: Boolean
|
||||
} = {
|
||||
percent : 0,
|
||||
visible : false
|
||||
}
|
||||
|
||||
/**
|
||||
* If loading is enabled {true} or not false
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
loadingStatus: Boolean = true
|
||||
|
||||
/**
|
||||
* If user date is loaded {true} or not {false}
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
defaultLoadDataStatus: Boolean = false
|
||||
|
||||
/**
|
||||
* If message popup is open {true} or not {false}
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
messageOpen: Boolean = false
|
||||
|
||||
/**
|
||||
* The text of popup global message
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
message: string = ''
|
||||
|
||||
/**
|
||||
* Window size
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
windowWidth: number = 0
|
||||
|
||||
/**
|
||||
* Window height
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
windowHeight: number = 0
|
||||
|
||||
/**
|
||||
* The text of website header
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
headerTitle: string = ''
|
||||
|
||||
/**
|
||||
* Top loading is visible {true} or not {false}
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
showTopLoading: Boolean = false
|
||||
|
||||
/**
|
||||
* Top loading message queue
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
topLoadingQueue: number = 0
|
||||
|
||||
/**
|
||||
* Temp date storage
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IGlobalState
|
||||
*/
|
||||
temp: any = {}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
let React = require('react')
|
||||
let ReactDOM = require('react-dom')
|
||||
let TestUtils = require('react-dom/test-utils')
|
||||
let expect = require('expect')
|
||||
let $ = require('jquery')
|
||||
|
||||
let {CommentWrite} = require('CommentWrite')
|
||||
|
||||
describe('CommentWrite', () => {
|
||||
it('should exist', () => {
|
||||
expect(CommentWrite).toExist()
|
||||
})
|
||||
|
||||
})
|
||||
@@ -26,7 +26,7 @@
|
||||
"inversify": "^4.3.0",
|
||||
"keycode": "^2.1.9",
|
||||
"lodash": "^4.17.4",
|
||||
"material-ui": "^0.19.3",
|
||||
"material-ui": "^0.19.4",
|
||||
"moment": "^2.18.1",
|
||||
"morgan": "^1.8.1",
|
||||
"node-env-file": "^0.1.8",
|
||||
@@ -60,13 +60,16 @@
|
||||
"@types/lodash": "^4.14.77",
|
||||
"@types/material-ui": "^0.18.2",
|
||||
"@types/node": "^8.0.33",
|
||||
"@types/prop-types": "^15.5.2",
|
||||
"@types/react": "^16.0.10",
|
||||
"@types/react-dom": "^16.0.1",
|
||||
"@types/react-event-listener": "^0.4.4",
|
||||
"@types/react-redux": "^5.0.10",
|
||||
"@types/react-router-dom": "^4.0.8",
|
||||
"@types/react-router-redux": "^5.0.8",
|
||||
"@types/react-tap-event-plugin": "0.0.30",
|
||||
"@types/redux-logger": "^3.0.4",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/webpack": "^3.0.13",
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.1.2",
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
var moment = require('moment');
|
||||
|
||||
console.log(moment().format());
|
||||
|
||||
// January 1st 1970 @ 12:00am -> 0
|
||||
// January 1st 1970 @ 12:01am -> -60
|
||||
|
||||
var now = moment();
|
||||
console.log('Current timestamp', now.unix());
|
||||
|
||||
var timestamp = 1459111648;
|
||||
var currentMoment = moment.unix(timestamp);
|
||||
console.log('current moment', currentMoment.format('MMM D, YY @ h:mm a'));
|
||||
|
||||
// January 3rd, 2016 @ 12:13 AM
|
||||
console.log('current moment', currentMoment.format('MMMM Do, YYYY @ h:mm A'));
|
||||
@@ -1,24 +1,27 @@
|
||||
|
||||
// - Import react components
|
||||
import moment from 'moment'
|
||||
import { push } from 'react-router-redux'
|
||||
|
||||
// -Import domain
|
||||
import { User } from 'domain/users'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { User } from 'core/domain/users'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
import { UserRegisterModel } from 'models/users/userRegisterModel'
|
||||
|
||||
// - Import action types
|
||||
import { AuthorizeActionType } from 'constants/authorizeActionType'
|
||||
|
||||
// - Import services
|
||||
import { IAuthorizeService } from 'services/authorize'
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
|
||||
import { IAuthorizeService } from 'core/services/authorize'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
@@ -55,10 +58,15 @@ export const dbLogout = () => {
|
||||
*
|
||||
* @param user for registering
|
||||
*/
|
||||
export const dbSignup = (user: User) => {
|
||||
return (dispatch: any, getState: any) => {
|
||||
export const dbSignup = (user: UserRegisterModel) => {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
dispatch(globalActions.showNotificationRequest())
|
||||
return authorizeService.registerUser(user).then((result) => {
|
||||
let newUser = new User()
|
||||
newUser.email = user.email
|
||||
newUser.password = user.password
|
||||
newUser.fullName = user.fullName
|
||||
|
||||
return authorizeService.registerUser(newUser).then((result) => {
|
||||
dispatch(signup({
|
||||
userId: result.uid,
|
||||
...user
|
||||
@@ -123,7 +131,7 @@ export const logout = () => {
|
||||
* User registeration call
|
||||
* @param user for registering
|
||||
*/
|
||||
export const signup = (user: User) => {
|
||||
export const signup = (user: UserRegisterModel) => {
|
||||
return {
|
||||
type: AuthorizeActionType.SIGNUP,
|
||||
payload: { ...user }
|
||||
@@ -1,7 +1,7 @@
|
||||
// - Import domain
|
||||
import { User } from 'domain/users'
|
||||
import { Circle, UserFollower } from 'domain/circles'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { User } from 'core/domain/users'
|
||||
import { Circle, UserFollower } from 'core/domain/circles'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import utility components
|
||||
import moment from 'moment'
|
||||
@@ -15,8 +15,8 @@ import * as postActions from 'actions/postActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { IServiceProvider,ServiceProvide } from 'factories'
|
||||
import { ICircleService } from 'services/circles'
|
||||
import { IServiceProvider,ServiceProvide } from 'core/factories'
|
||||
import { ICircleService } from 'core/services/circles'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const circleService: ICircleService = serviceProvider.createCircleService()
|
||||
@@ -51,7 +51,7 @@ export let dbAddCircle = (circleName: string) => {
|
||||
* @param {string} cid is circle identifier
|
||||
* @param {User} userFollowing is the user for following
|
||||
*/
|
||||
export let dbAddFollowingUser = (cid: string, userFollowing: User) => {
|
||||
export let dbAddFollowingUser = (cid: string, userFollowing: UserFollower) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
@@ -2,8 +2,8 @@
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import domain
|
||||
import { Comment } from 'domain/comments'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { CommentActionType } from 'constants/commentActionType'
|
||||
@@ -12,8 +12,8 @@ import { CommentActionType } from 'constants/commentActionType'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
import { ICommentService } from 'services/comments'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { ICommentService } from 'core/services/comments'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const commentService: ICommentService = serviceProvider.createCommentService()
|
||||
@@ -26,7 +26,7 @@ const commentService: ICommentService = serviceProvider.createCommentService()
|
||||
* @param {object} newComment user comment
|
||||
* @param {function} callBack will be fired when server responsed
|
||||
*/
|
||||
export const dbAddComment = (ownerPostUserId: string,newComment: Comment, callBack: Function) => {
|
||||
export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment, callBack: Function) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
dispatch(globalActions.showTopLoading())
|
||||
@@ -74,14 +74,9 @@ export const dbGetComments = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
|
||||
return commentService.getComments()
|
||||
.then((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
|
||||
return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
|
||||
dispatch(addCommentList(comments))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,24 +126,20 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => {
|
||||
* @param {string} id of comment
|
||||
* @param {string} postId is the identifier of the post which comment belong to
|
||||
*/
|
||||
export const dbDeleteComment = (id: string, postId: string) => {
|
||||
export const dbDeleteComment = (id?: string | null, postId?: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
if (id === undefined || id === null) {
|
||||
dispatch(globalActions.showErrorMessage('comment id can not be null or undefined'))
|
||||
}
|
||||
|
||||
console.log('====================================')
|
||||
console.log(id,postId)
|
||||
console.log('====================================')
|
||||
dispatch(globalActions.showTopLoading())
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return commentService.deleteComment(id,postId)
|
||||
return commentService.deleteComment(id!,postId!)
|
||||
.then(() => {
|
||||
dispatch(deleteComment(id, postId))
|
||||
dispatch(deleteComment(id!, postId!))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
}, (error: SocialError) => {
|
||||
@@ -146,7 +146,7 @@ export const hideTopLoading = () => {
|
||||
/**
|
||||
* Store temp data
|
||||
*/
|
||||
export const temp = (data: string) => {
|
||||
export const temp = (data: any) => {
|
||||
return{
|
||||
type: GlobalActionType.TEMP,
|
||||
payload: data
|
||||
@@ -154,6 +154,16 @@ export const temp = (data: string) => {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear temp data
|
||||
*/
|
||||
export const clearTemp = () => {
|
||||
return{
|
||||
type: GlobalActionType.CLEAR_TEMP
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// - Load data for guest
|
||||
export const loadDataGuest = () => {
|
||||
// tslint:disable-next-line:no-empty
|
||||
@@ -2,8 +2,8 @@
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import domain
|
||||
import { Image } from 'domain/imageGallery'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { ImageGalleryActionType } from 'constants/imageGalleryActionType'
|
||||
@@ -14,8 +14,8 @@ import * as globalActions from 'actions/globalActions'
|
||||
// - Import app API
|
||||
import FileAPI from 'api/FileAPI'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
import { IImageGalleryService } from 'services/imageGallery'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IImageGalleryService } from 'core/services/imageGallery'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const imageGalleryService: IImageGalleryService = serviceProvider.createImageGalleryService()
|
||||
@@ -2,8 +2,8 @@
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import domain
|
||||
import { Notification } from 'domain/notifications'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { Notification } from 'core/domain/notifications'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { NotificationActionType } from 'constants/notificationActionType'
|
||||
@@ -12,8 +12,8 @@ import { NotificationActionType } from 'constants/notificationActionType'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
import { INotificationService } from 'services/notifications'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { INotificationService } from 'core/services/notifications'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const notificationService: INotificationService = serviceProvider.createNotificationService()
|
||||
@@ -50,18 +50,17 @@ export const dbAddNotification = (newNotify: Notification) => {
|
||||
* Get all notificaitions from database
|
||||
*/
|
||||
export const dbGetNotifications = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
return (dispatch: Function , getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
|
||||
return notificationService.getNotifications(uid)
|
||||
.then((notifies: { [notifyId: string]: Notification }) => {
|
||||
Object.keys(notifies).forEach((key => {
|
||||
if (!getState().user.info[notifies[key].notifierUserId]) {
|
||||
dispatch(userActions.dbGetUserInfoByUserId(notifies[key].notifierUserId,''))
|
||||
return notificationService.getNotifications(uid,
|
||||
(notifications: { [notifyId: string]: Notification} ) => {
|
||||
Object.keys(notifications).forEach((key => {
|
||||
if (!getState().user.info[notifications[key].notifierUserId]) {
|
||||
dispatch(userActions.dbGetUserInfoByUserId(notifications[key].notifierUserId,''))
|
||||
}
|
||||
}))
|
||||
dispatch(addNotifyList(notifies))
|
||||
dispatch(addNotifyList(notifications))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
import { Action } from 'redux'
|
||||
|
||||
// - Import domain
|
||||
import { Post } from 'domain/posts'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { Post } from 'core/domain/posts'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import utility components
|
||||
import moment from 'moment'
|
||||
@@ -14,8 +14,8 @@ import { PostActionType } from 'constants/postActionType'
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
import { IPostService } from 'services/posts'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IPostService } from 'core/services/posts'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const postService: IPostService = serviceProvider.createPostService()
|
||||
@@ -68,7 +68,7 @@ export let dbAddPost = (newPost: any, callBack: Function) => {
|
||||
* @param {object} newPost
|
||||
* @param {function} callBack
|
||||
*/
|
||||
export const dbAddImagePost = (newPost: any, callBack: Function) => {
|
||||
export const dbAddImagePost = (newPost: Post, callBack: Function) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
dispatch(globalActions.showTopLoading())
|
||||
@@ -82,8 +82,8 @@ export const dbAddImagePost = (newPost: any, callBack: Function) => {
|
||||
viewCount: 0,
|
||||
body: newPost.body,
|
||||
ownerUserId: uid,
|
||||
ownerDisplayName: newPost.name,
|
||||
ownerAvatar: newPost.avatar,
|
||||
ownerDisplayName: newPost.ownerDisplayName,
|
||||
ownerAvatar: newPost.ownerAvatar,
|
||||
lastEditDate: 0,
|
||||
tags: newPost.tags || [],
|
||||
commentCounter: 0,
|
||||
@@ -115,7 +115,7 @@ export const dbAddImagePost = (newPost: any, callBack: Function) => {
|
||||
* @param {object} newPost
|
||||
* @param {func} callBack //TODO: anti pattern should change to parent state or move state to redux
|
||||
*/
|
||||
export const dbUpdatePost = (newPost: any, callBack: Function) => {
|
||||
export const dbUpdatePost = (newPost: Post, callBack: Function) => {
|
||||
console.log(newPost)
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
@@ -126,7 +126,7 @@ export const dbUpdatePost = (newPost: any, callBack: Function) => {
|
||||
|
||||
// Write the new data simultaneously in the list
|
||||
let updates: any = {}
|
||||
let post: Post = getState().post.userPosts[uid][newPost.id]
|
||||
let post: Post = getState().post.userPosts[uid][newPost.id!]
|
||||
let updatedPost: Post = {
|
||||
postTypeId: post.postTypeId,
|
||||
creationDate: post.creationDate,
|
||||
@@ -1,8 +1,8 @@
|
||||
// - Import react components
|
||||
|
||||
// - Import domain
|
||||
import { Profile } from 'domain/users'
|
||||
import { SocialError } from 'domain/common'
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { UserActionType } from 'constants/userActionType'
|
||||
@@ -11,8 +11,8 @@ import { UserActionType } from 'constants/userActionType'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
import { IUserService } from 'services/users'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IUserService } from 'core/services/users'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const userService: IUserService = serviceProvider.createUserService()
|
||||
@@ -48,9 +48,14 @@ export const dbGetUserInfo = () => {
|
||||
* @param {string} callerKey
|
||||
*/
|
||||
export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
if (uid) {
|
||||
|
||||
let caller = getState().global.temp.caller
|
||||
if ( caller && caller.indexOf(`dbGetUserInfoByUserId-${uid}`) > -1) {
|
||||
return
|
||||
}
|
||||
dispatch(globalActions.temp({caller: `dbGetUserInfoByUserId-${uid}`}))
|
||||
return userService.getUserProfile(uid).then((userProfile: Profile) => {
|
||||
|
||||
dispatch(addUserInfo(uid, {
|
||||
@@ -76,7 +81,6 @@ export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updata user information
|
||||
* @param {object} newInfo
|
||||
@@ -4,14 +4,14 @@ import moment from 'moment'
|
||||
import { VoteActionType } from 'constants/voteActionType'
|
||||
|
||||
// - Import domain
|
||||
import { Vote } from 'domain/votes'
|
||||
import { Vote } from 'core/domain/votes'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'factories'
|
||||
import { IVoteService } from 'services/votes'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IVoteService } from 'core/services/votes'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const voteService: IVoteService = serviceProvider.createVoteService()
|
||||
47
src/api/CircleAPI.ts
Normal file
47
src/api/CircleAPI.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Circle, UserFollower } from 'core/domain/circles'
|
||||
|
||||
/**
|
||||
* Get the circles' id which the specify users is in that circle
|
||||
* @param {object} circles
|
||||
* @param {string} followingId
|
||||
*/
|
||||
export const getUserBelongCircles = (circles: {[circleId: string]: Circle},followingId: string) => {
|
||||
let userBelongCircles: string[] = []
|
||||
Object.keys(circles).forEach((cid) => {
|
||||
if (cid.trim() !== '-Followers' && circles[cid].users) {
|
||||
let isExist = Object.keys(circles[cid].users).indexOf(followingId) > -1
|
||||
if (isExist) {
|
||||
userBelongCircles.push(cid)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return userBelongCircles
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the following users
|
||||
* @param {object} circles
|
||||
*/
|
||||
export const getFollowingUsers = (circles: {[circleId: string]: Circle}) => {
|
||||
let followingUsers: {[userId: string]: UserFollower} = {}
|
||||
Object.keys(circles).forEach((cid) => {
|
||||
if (cid.trim() !== '-Followers' && circles[cid].users) {
|
||||
Object.keys(circles[cid].users).forEach((userId) => {
|
||||
let isExist = Object.keys(followingUsers).indexOf(userId) > -1
|
||||
if (!isExist) {
|
||||
followingUsers[userId] = {
|
||||
...circles[cid].users[userId]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
return followingUsers
|
||||
}
|
||||
|
||||
export default {
|
||||
getUserBelongCircles,
|
||||
getFollowingUsers
|
||||
}
|
||||
147
src/api/FileAPI.ts
Normal file
147
src/api/FileAPI.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
// - Import react component
|
||||
import { storageRef } from 'data/firebaseClient'
|
||||
|
||||
// - Interface declaration
|
||||
interface FileReaderEventTarget extends EventTarget {
|
||||
result: string
|
||||
}
|
||||
|
||||
interface FileReaderEvent extends Event {
|
||||
target: FileReaderEventTarget
|
||||
getMessage (): string
|
||||
}
|
||||
|
||||
// - Get file Extension
|
||||
const getExtension = (fileName: string) => {
|
||||
let re: RegExp = /(?:\.([^.]+))?$/
|
||||
return re.exec(fileName)![1]
|
||||
}
|
||||
|
||||
// Converts image to canvas returns new canvas element
|
||||
const convertImageToCanvas = (image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap) => {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
canvas.getContext('2d')!.drawImage(image, 0, 0)
|
||||
|
||||
return canvas
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload image on the server
|
||||
* @param {file} file
|
||||
* @param {string} fileName
|
||||
*/
|
||||
const uploadImage = (file: any, fileName: string, progress: (percentage: number, status: boolean) => void) => {
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
// Create a storage refrence
|
||||
let storegeFile = storageRef.child(`images/${fileName}`)
|
||||
|
||||
// Upload file
|
||||
let task = storegeFile.put(file)
|
||||
task.then((result) => {
|
||||
resolve(result)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
// Upload storage bar
|
||||
task.on('state_changed', (snapshot: any) => {
|
||||
let percentage: number = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
|
||||
progress(percentage, true)
|
||||
}, (error) => {
|
||||
console.log('========== Upload Image ============')
|
||||
console.log(error)
|
||||
console.log('====================================')
|
||||
|
||||
}, () => {
|
||||
progress(100, false)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint image size
|
||||
* @param {file} file
|
||||
* @param {number} maxWidth
|
||||
* @param {number} maxHeight
|
||||
*/
|
||||
const constraintImage = (file: File,fileName: string, maxWidth?: number, maxHeight?: number) => {
|
||||
// Ensure it's an image
|
||||
if (file.type.match(/image.*/)) {
|
||||
// Load the image
|
||||
let reader = new FileReader()
|
||||
reader.onload = function (readerEvent: FileReaderEvent) {
|
||||
let image = new Image()
|
||||
image.onload = function (imageEvent: Event) {
|
||||
|
||||
// Resize the image
|
||||
let canvas: HTMLCanvasElement = document.createElement('canvas')
|
||||
let maxSize: number = 986// TODO : pull max size from a site config
|
||||
let width: number = image.width
|
||||
let height: number = image.height
|
||||
if (width > height) {
|
||||
if (width > maxSize) {
|
||||
height *= maxSize / width
|
||||
width = maxSize
|
||||
}
|
||||
} else {
|
||||
if (height > maxSize) {
|
||||
width *= maxSize / height
|
||||
height = maxSize
|
||||
}
|
||||
}
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
canvas.getContext('2d')!.drawImage(image, 0, 0, width, height)
|
||||
let dataUrl = canvas.toDataURL()
|
||||
let resizedImage = dataURLToBlob(dataUrl)
|
||||
let evt = new CustomEvent('onSendResizedImage', { detail: {resizedImage,fileName} })
|
||||
window.dispatchEvent(evt)
|
||||
|
||||
}
|
||||
image.src = readerEvent.target.result
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data URL to blob
|
||||
* @param {object} dataURL
|
||||
*/
|
||||
const dataURLToBlob = (dataURL: string) => {
|
||||
|
||||
let BASE64_MARKER = 'base64,'
|
||||
if (dataURL.indexOf(BASE64_MARKER) === -1) {
|
||||
let parts = dataURL.split(',')
|
||||
let contentType = parts[0].split(':')[1]
|
||||
let raw = parts[1]
|
||||
|
||||
return new Blob([raw], {type: contentType})
|
||||
}
|
||||
|
||||
let parts = dataURL.split(BASE64_MARKER)
|
||||
let contentType = parts[0].split(':')[1]
|
||||
let raw = window.atob(parts[1])
|
||||
let rawLength = raw.length
|
||||
|
||||
let uInt8Array = new Uint8Array(rawLength)
|
||||
|
||||
for (let i = 0 ;i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i)
|
||||
}
|
||||
|
||||
return new Blob([uInt8Array], {type: contentType})
|
||||
}
|
||||
|
||||
export default {
|
||||
dataURLToBlob,
|
||||
convertImageToCanvas,
|
||||
getExtension,
|
||||
constraintImage,
|
||||
uploadImage
|
||||
|
||||
}
|
||||
29
src/api/PostAPI.ts
Normal file
29
src/api/PostAPI.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
// Get tags from post content
|
||||
export const detectTags: (content: string, character: string) => string[] = (content: string, character: string) => {
|
||||
|
||||
return content.split(' ').filter((word) => {
|
||||
return (word.slice(0,1) === character)
|
||||
})
|
||||
|
||||
}
|
||||
export const getContentTags = (content: string) => {
|
||||
let newTags: string[] = []
|
||||
let tags = detectTags(content,'#')
|
||||
tags.forEach((tag) => {
|
||||
newTags.push(tag.slice(1))
|
||||
})
|
||||
return newTags
|
||||
}
|
||||
|
||||
export const sortObjectsDate = (objects: any) => {
|
||||
let sortedObjects = objects
|
||||
|
||||
// Sort posts with creation date
|
||||
sortedObjects.sort((a: any, b: any) => {
|
||||
return parseInt(b.creationDate,10) - parseInt(a.creationDate,10)
|
||||
|
||||
})
|
||||
|
||||
return sortedObjects
|
||||
}
|
||||
30
src/api/StringAPI.ts
Normal file
30
src/api/StringAPI.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// - Import react component
|
||||
import { storageRef } from 'data/firebaseClient'
|
||||
|
||||
// - Import actions
|
||||
|
||||
const isValidEmail = (email: string) => {
|
||||
let re = /^(([^<>()\[\]\\.,:\s@"]+(\.[^<>()\[\]\\.,:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
return re.test(email)
|
||||
}
|
||||
|
||||
function queryString (name: string, url: string = window.location.href) {
|
||||
name = name.replace(/[[]]/g, '\\$&')
|
||||
|
||||
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i')
|
||||
const results = regex.exec(url)
|
||||
|
||||
if (!results) {
|
||||
return null
|
||||
}
|
||||
if (!results[2]) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '))
|
||||
}
|
||||
|
||||
export default {
|
||||
isValidEmail,
|
||||
queryString
|
||||
}
|
||||
270
src/components/circle/CircleComponent.tsx
Normal file
270
src/components/circle/CircleComponent.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { push } from 'react-router-redux'
|
||||
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
import SvgGroup from 'material-ui/svg-icons/action/group-work'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
|
||||
import IconMenu from 'material-ui/IconMenu'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import IconButtonElement from 'layouts/IconButtonElement'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import SvgClose from 'material-ui/svg-icons/navigation/close'
|
||||
import AppBar from 'material-ui/AppBar'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'components/userAvatar'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'actions/circleActions'
|
||||
|
||||
import { ICircleComponentProps } from './ICircleComponentProps'
|
||||
import { ICircleComponentState } from './ICircleComponentState'
|
||||
import { Circle } from 'core/domain/circles'
|
||||
import { Profile } from 'core/domain/users/profile'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CircleComponent extends Component<ICircleComponentProps, ICircleComponentState> {
|
||||
|
||||
styles = {
|
||||
userListItem: {
|
||||
backgroundColor: '#e2e2e2'
|
||||
},
|
||||
rightIconMenu: {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '12px'
|
||||
},
|
||||
settingOverlay: {
|
||||
background: 'rgba(0,0,0,0.12)'
|
||||
},
|
||||
settingContent: {
|
||||
maxWidth: '400px'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: ICircleComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* If is true circle is open to show users in circle list
|
||||
*/
|
||||
open: false,
|
||||
/**
|
||||
* Circle name on change
|
||||
*/
|
||||
circleName: this.props.circle.name,
|
||||
/**
|
||||
* Save operation will be disable if user doesn't meet requirement
|
||||
*/
|
||||
disabledSave: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleToggleCircle = this.handleToggleCircle.bind(this)
|
||||
this.handleDeleteCircle = this.handleDeleteCircle.bind(this)
|
||||
this.handleUpdateCircle = this.handleUpdateCircle.bind(this)
|
||||
this.handleChangeCircleName = this.handleChangeCircleName.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle chage circle name
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleChangeCircleName = (evt: any) => {
|
||||
const { value } = evt.target
|
||||
this.setState({
|
||||
circleName: value,
|
||||
disabledSave: (!value || value.trim() === '')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's circle
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleUpdateCircle = () => {
|
||||
const { circleName } = this.state
|
||||
if (circleName && circleName.trim() !== '') {
|
||||
this.props.updateCircle!({ name: circleName, id: this.props.id })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete circle
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleDeleteCircle = () => {
|
||||
this.props.deleteCircle!(this.props.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle circle to close/open
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleToggleCircle = () => {
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
})
|
||||
}
|
||||
|
||||
userList = () => {
|
||||
const { users } = this.props.circle
|
||||
const { userInfo } = this.props
|
||||
let usersParsed: any = []
|
||||
|
||||
if (users) {
|
||||
Object.keys(users).forEach((key, index) => {
|
||||
const { fullName } = users[key]
|
||||
let avatar = userInfo && userInfo[key] ? userInfo[key].avatar || '' : ''
|
||||
usersParsed.push(<ListItem
|
||||
key={`${this.props.id}.${key}`}
|
||||
style={this.styles.userListItem as any}
|
||||
value={2}
|
||||
primaryText={fullName}
|
||||
leftAvatar={<UserAvatar fullName={fullName} fileName={avatar as any} />}
|
||||
onClick={() => this.props.goTo!(`/${key}`)}
|
||||
/>)
|
||||
|
||||
})
|
||||
return usersParsed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Right icon menue of circle
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
// tslint:disable-next-line:member-ordering
|
||||
rightIconMenu: any = (
|
||||
<IconMenu iconButtonElement={IconButtonElement} style={this.styles.rightIconMenu as any}>
|
||||
<MenuItem primaryText='Delete circle' onClick={this.handleDeleteCircle} />
|
||||
<MenuItem primaryText='Circle settings' onClick={this.props.openCircleSettings} />
|
||||
</IconMenu>
|
||||
)
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const circleTitle = (
|
||||
<div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
<div style={{ paddingRight: '10px' }}>
|
||||
<SvgClose onClick={this.props.closeCircleSettings} hoverColor={grey400} style={{ cursor: 'pointer' }} />
|
||||
</div>
|
||||
<div style={{
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
flex: '1 1',
|
||||
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif'
|
||||
}}>
|
||||
Circle settings
|
||||
</div>
|
||||
<div style={{ marginTop: '-9px' }}>
|
||||
<FlatButton label='SAVE' primary={true} disabled={this.state.disabledSave} onClick={this.handleUpdateCircle} />
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<ListItem
|
||||
key={this.props.id}
|
||||
style={{ backgroundColor: '#fff', borderBottom: '1px solid rgba(0,0,0,0.12)', height: '72px', padding: '12px 0' }}
|
||||
primaryText={<span style={{ color: 'rgba(0,0,0,0.87)', fontSize: '16px', marginRight: '8px', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }}>{this.props.circle.name}</span>}
|
||||
leftIcon={<SvgGroup style={{ width: '40px', height: '40px', transform: 'translate(0px, -9px)', fill: '#bdbdbd' }} />}
|
||||
rightIconButton={this.rightIconMenu}
|
||||
initiallyOpen={false}
|
||||
onClick={this.handleToggleCircle}
|
||||
open={this.state.open}
|
||||
nestedItems={this.userList()}
|
||||
>
|
||||
<Dialog
|
||||
key={this.props.id}
|
||||
title={circleTitle}
|
||||
modal={false}
|
||||
open={this.props.openSetting!}
|
||||
onRequestClose={this.props.closeCircleSettings}
|
||||
overlayStyle={this.styles.settingOverlay as any}
|
||||
contentStyle={this.styles.settingContent as any}
|
||||
>
|
||||
<div>
|
||||
<TextField
|
||||
hintText='Circle name'
|
||||
floatingLabelText='Circle name'
|
||||
onChange={this.handleChangeCircleName}
|
||||
value={this.state.circleName}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</ListItem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ICircleComponentProps) => {
|
||||
let { uid } = ownProps
|
||||
return {
|
||||
deleteCircle: (id: string) => dispatch(circleActions.dbDeleteCircle(id)),
|
||||
updateCircle: (circle: Circle) => dispatch(circleActions.dbUpdateCircle(circle)),
|
||||
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid, ownProps.id)),
|
||||
openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid, ownProps.id)),
|
||||
goTo: (url: string) => dispatch(push(url))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: ICircleComponentProps) => {
|
||||
let { uid } = state.authorize
|
||||
return {
|
||||
openSetting: state.circle ? (state.circle.userCircles[uid] ? (state.circle.userCircles[uid][ownProps.id].openCircleSettings || false) : false) : false,
|
||||
userInfo: state.user.info
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CircleComponent as any)
|
||||
84
src/components/circle/ICircleComponentProps.ts
Normal file
84
src/components/circle/ICircleComponentProps.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Circle } from 'core/domain/circles'
|
||||
|
||||
export interface ICircleComponentProps {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Circle}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
circle: Circle
|
||||
|
||||
/**
|
||||
* Circle identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
uid: string
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
updateCircle?: Function
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
deleteCircle?: Function
|
||||
|
||||
/**
|
||||
* Users profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
userInfo?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* Close setting box of circle
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
closeCircleSettings?: any
|
||||
|
||||
/**
|
||||
* Circle setting dialog is open {true} or not {false}
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
openSetting?: boolean
|
||||
|
||||
/**
|
||||
* Change route location
|
||||
*
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => void
|
||||
|
||||
/**
|
||||
* Open setting box for a circle
|
||||
*
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
openCircleSettings?: () => any
|
||||
}
|
||||
27
src/components/circle/ICircleComponentState.ts
Normal file
27
src/components/circle/ICircleComponentState.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
export interface ICircleComponentState {
|
||||
|
||||
/**
|
||||
* Circle name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentState
|
||||
*/
|
||||
circleName: string
|
||||
|
||||
/**
|
||||
* If circle user list is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICircleComponentState
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Save button is disabled {true} or not false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICircleComponentState
|
||||
*/
|
||||
disabledSave: boolean
|
||||
}
|
||||
2
src/components/circle/index.ts
Normal file
2
src/components/circle/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CircleComponent from './CircleComponent'
|
||||
export default CircleComponent
|
||||
@@ -1,13 +1,11 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink} from 'react-router-dom'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { createAction as action } from 'redux-actions'
|
||||
import moment from 'moment'
|
||||
import Linkify from 'react-linkify'
|
||||
|
||||
|
||||
// - Import material UI libraries
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
import Divider from 'material-ui/Divider'
|
||||
@@ -21,27 +19,24 @@ import MenuItem from 'material-ui/MenuItem'
|
||||
import TextField from 'material-ui/TextField'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'UserAvatar'
|
||||
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import action types
|
||||
import * as types from 'actionTypes'
|
||||
|
||||
import * as types from 'constants/actionTypes'
|
||||
|
||||
// - Import actions
|
||||
import * as commentActions from 'commentActions'
|
||||
import * as userActions from 'userActions'
|
||||
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { ICommentComponentProps } from './ICommentComponentProps'
|
||||
import { ICommentComponentState } from './ICommentComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class Comment extends Component {
|
||||
|
||||
|
||||
export class CommentComponent extends Component<ICommentComponentProps,ICommentComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
@@ -55,20 +50,84 @@ export class Comment extends Component {
|
||||
/**
|
||||
* If it's true the comment is disable to write
|
||||
*/
|
||||
disableComments: PropTypes.bool,
|
||||
disableComments: PropTypes.bool
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM styles
|
||||
*
|
||||
*
|
||||
* @memberof CommentComponent
|
||||
*/
|
||||
styles = {
|
||||
comment: {
|
||||
marginBottom: '12px'
|
||||
},
|
||||
iconButton: {
|
||||
width: 16,
|
||||
height: 16
|
||||
|
||||
},
|
||||
author: {
|
||||
fontSize: '13px',
|
||||
paddingRight: '10px',
|
||||
fontWeight: 400,
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
|
||||
},
|
||||
commentBody: {
|
||||
fontSize: '13px',
|
||||
lineHeight: '20px',
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
fontWeight: 300,
|
||||
height: '',
|
||||
display: 'block'
|
||||
|
||||
},
|
||||
rightIconMenuItem: {
|
||||
fontSize: '14px'
|
||||
},
|
||||
textarea: {
|
||||
fontWeight: 100,
|
||||
fontSize: '14px',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
outline: 'none',
|
||||
resize: 'none'
|
||||
},
|
||||
cancel: {
|
||||
float: 'right',
|
||||
clear: 'both',
|
||||
zIndex: 5,
|
||||
margin: '0px 5px 5px 0px',
|
||||
fontWeight: 400
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof CommentComponent
|
||||
*/
|
||||
textareaRef: any
|
||||
divCommentRef: any
|
||||
inputText: any
|
||||
divComment: any
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: ICommentComponentProps) {
|
||||
super(props)
|
||||
|
||||
this.textareaRef = i => { this.inputText = i }
|
||||
this.divCommentRef = i => { this.divComment = i }
|
||||
this.textareaRef = (i: any) => { this.inputText = i }
|
||||
this.divCommentRef = (i: any) => { this.divComment = i }
|
||||
|
||||
//Defaul state
|
||||
// Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* Comment text
|
||||
@@ -85,7 +144,7 @@ export class Comment extends Component {
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
isPostOwner: PropTypes.bool
|
||||
isPostOwner: false
|
||||
|
||||
}
|
||||
|
||||
@@ -98,22 +157,20 @@ export class Comment extends Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle show edit comment
|
||||
* @param {event} evt is an event passed by clicking on edit button
|
||||
*/
|
||||
handleEditComment = (evt) => {
|
||||
handleEditComment = (evt: any) => {
|
||||
this.inputText.style.height = this.divComment.clientHeight + 'px'
|
||||
this.props.openEditor()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle cancel edit
|
||||
* @param {event} evt is an event passed by clicking on cancel button
|
||||
*/
|
||||
handleCancelEdit = (evt) => {
|
||||
handleCancelEdit = (evt: any) => {
|
||||
|
||||
this.setState({
|
||||
text: this.state.initialText
|
||||
@@ -125,7 +182,7 @@ export class Comment extends Component {
|
||||
* Handle edit comment
|
||||
* @param {event} evt is an event passed by clicking on post button
|
||||
*/
|
||||
handleUpdateComment = (evt) => {
|
||||
handleUpdateComment = (evt: any) => {
|
||||
|
||||
this.props.update(this.props.comment.id, this.props.comment.postId, this.state.text)
|
||||
this.setState({
|
||||
@@ -134,14 +191,12 @@ export class Comment extends Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* When comment text changed
|
||||
* @param {event} evt is an event passed by change comment text callback funciton
|
||||
* @param {string} data is the comment text which user writes
|
||||
*/
|
||||
handleOnChange = (evt) => {
|
||||
handleOnChange = (evt: any) => {
|
||||
const data = evt.target.value
|
||||
this.inputText.style.height = evt.target.scrollHeight + 'px'
|
||||
if (data.length === 0 || data.trim() === '' || data.trim() === this.state.initialText) {
|
||||
@@ -149,8 +204,7 @@ export class Comment extends Component {
|
||||
text: data,
|
||||
editDisabled: true
|
||||
})
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.setState({
|
||||
text: data,
|
||||
editDisabled: false
|
||||
@@ -159,119 +213,81 @@ export class Comment extends Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete a comment
|
||||
* @param {event} evt an event passed by click on delete comment
|
||||
* @param {string} id comment identifire
|
||||
* @param {string} postId post identifier which comment belong to
|
||||
*/
|
||||
handleDelete = (evt, id, postId) => {
|
||||
handleDelete = (evt: any, id?: string| null, postId?: string) => {
|
||||
this.props.delete(id, postId)
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentWillMount () {
|
||||
const {userId} = this.props.comment
|
||||
if (!this.props.isCommentOwner && !this.props.info[userId]) {
|
||||
if (!this.props.isCommentOwner && !this.props.info[userId!]) {
|
||||
this.props.getUserInfo()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
|
||||
/**
|
||||
* DOM styles
|
||||
*
|
||||
*
|
||||
* @memberof Comment
|
||||
*/
|
||||
const styles = {
|
||||
comment: {
|
||||
marginBottom: '12px'
|
||||
},
|
||||
iconButton: {
|
||||
width: 16,
|
||||
height: 16
|
||||
|
||||
},
|
||||
author:{
|
||||
fontSize: "13px",
|
||||
paddingRight: "10px",
|
||||
fontWeight: 400,
|
||||
color: "rgba(0,0,0,0.87)",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden"
|
||||
|
||||
},
|
||||
commentBody:{
|
||||
fontSize: "13px",
|
||||
lineHeight: "20px",
|
||||
color: "rgba(0,0,0,0.87)",
|
||||
fontWeight: 300,
|
||||
height: "",
|
||||
display: "block"
|
||||
|
||||
}
|
||||
}
|
||||
render () {
|
||||
|
||||
/**
|
||||
* Comment object from props
|
||||
*/
|
||||
const {comment} = this.props
|
||||
|
||||
|
||||
const iconButtonElement = (
|
||||
<IconButton style={styles.iconButton} iconStyle={styles.iconButton}
|
||||
<IconButton style={this.styles.iconButton} iconStyle={this.styles.iconButton}
|
||||
touch={true}
|
||||
>
|
||||
<MoreVertIcon color={grey400} viewBox='9 0 24 24' />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
|
||||
const RightIconMenu = () => (
|
||||
<IconMenu iconButtonElement={iconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "4px" }}>
|
||||
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
|
||||
{this.props.isCommentOwner ? (<MenuItem style={{ fontSize: "14px" }} onClick={this.handleEditComment}>Edit</MenuItem>): ''}
|
||||
{(this.props.isCommentOwner || this.props.isPostOwner) ? ( <MenuItem style={{ fontSize: "14px" }} onClick={(evt) => this.handleDelete(evt, comment.id, comment.postId)}>Delete</MenuItem>): ''}
|
||||
<IconMenu iconButtonElement={iconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
|
||||
{this.props.isCommentOwner ? (<MenuItem style={this.styles.rightIconMenuItem} onClick={this.handleEditComment}>Edit</MenuItem>) : ''}
|
||||
{(this.props.isCommentOwner || this.props.isPostOwner) ? ( <MenuItem style={{ fontSize: '14px' }} onClick={(evt) => this.handleDelete(evt, comment.id, comment.postId)}>Delete</MenuItem>) : ''}
|
||||
</IconMenu>
|
||||
)
|
||||
|
||||
const Author = () => (
|
||||
<div style={{ marginTop: "-11px" }}>
|
||||
<span style={styles.author}>{comment.userDisplayName}</span><span style={{
|
||||
<div style={{ marginTop: '-11px' }}>
|
||||
<span style={this.styles.author as any}>{comment.userDisplayName}</span><span style={{
|
||||
fontWeight: 100,
|
||||
fontSize: "10px"
|
||||
}}>{moment.unix(comment.creationDate).fromNow()}</span>
|
||||
fontSize: '10px'
|
||||
}}>{moment.unix(comment.creationDate!).fromNow()}</span>
|
||||
</div>
|
||||
)
|
||||
const commentBody = (
|
||||
<p style={styles.commentBody}>{comment.text}</p>
|
||||
<p style={this.styles.commentBody as any}>{comment.text}</p>
|
||||
)
|
||||
|
||||
const {userId} = comment
|
||||
|
||||
return (
|
||||
<div className="animate-top" style={styles.comment} key={comment.id}>
|
||||
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", padding: "", display: (!this.state.display ? "block" : "none") }}>
|
||||
<div style={{ marginLeft: "0px", padding: "16px 56px 0px 72px", position: "relative" }}>
|
||||
<NavLink to={`/${userId}`}><UserAvatar fullName={this.props.fullName} fileName={this.props.avatar} style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", position: "absolute", top: "8px", left: "16px" }} size={36} /></NavLink>
|
||||
<div className='animate-top' style={this.styles.comment} key={comment.id!}>
|
||||
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', padding: '', display: (!this.state.display ? 'block' : 'none') }}>
|
||||
<div style={{ marginLeft: '0px', padding: '16px 56px 0px 72px', position: 'relative' }}>
|
||||
<NavLink to={`/${userId}`}><UserAvatarComponent fullName={this.props.fullName} fileName={this.props.avatar} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', position: 'absolute', top: '8px', left: '16px' }} size={36} /></NavLink>
|
||||
<NavLink to={`/${userId}`}> <Author /></NavLink>
|
||||
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments )? '' : (<RightIconMenu />)}
|
||||
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}>
|
||||
<textarea ref={this.textareaRef} className="animate2-top10" style={{ fontWeight: 100, fontSize: "14px", border: "none", width: "100%", outline: "none", resize: "none", display: (this.props.comment.editorStatus ? 'block' : 'none') }} onChange={this.handleOnChange} value={this.state.text}></textarea>
|
||||
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments ) ? '' : (<RightIconMenu />)}
|
||||
<div style={{ outline: 'none', marginLeft: '16px', flex: 'auto', flexGrow: 1 }}>
|
||||
<textarea ref={this.textareaRef} className='animate2-top10' style={ Object.assign({}, this.styles.textarea, { display: (this.props.comment.editorStatus ? 'block' : 'none') })} onChange={this.handleOnChange} value={this.state.text!}></textarea>
|
||||
<Linkify properties={{target: '_blank', style: {color: 'blue'}}}>
|
||||
<div ref={this.divCommentRef} className="animate2-top10" style={{ fontWeight: 100, fontSize: "14px", height: "100%", border: "none", width: "100%", outline: "none", resize: "none", display: (!this.props.comment.editorStatus ? 'block' : 'none') }}>{this.state.text}</div>
|
||||
<div ref={this.divCommentRef} className='animate2-top10' style={{ fontWeight: 100, fontSize: '14px', height: '100%', border: 'none', width: '100%', outline: 'none', resize: 'none', display: (!this.props.comment.editorStatus ? 'block' : 'none') }}>{this.state.text}</div>
|
||||
</Linkify>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: (this.props.comment.editorStatus ? "flex" : "none"), flexDirection: "row-reverse" }}>
|
||||
<FlatButton primary={true} disabled={this.state.editDisabled} label="Update" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handleUpdateComment} />
|
||||
<FlatButton primary={true} label="Cancel" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handleCancelEdit} />
|
||||
<div style={{ display: (this.props.comment.editorStatus ? 'flex' : 'none'), flexDirection: 'row-reverse' }}>
|
||||
<FlatButton primary={true} disabled={this.state.editDisabled} label='Update' style={{ float: 'right', clear: 'both', zIndex: 5, margin: '0px 5px 5px 0px', fontWeight: 400 }} onClick={this.handleUpdateComment} />
|
||||
<FlatButton primary={true} label='Cancel' style={this.styles.cancel as any} onClick={this.handleCancelEdit} />
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
@@ -280,20 +296,19 @@ export class Comment extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: any) => {
|
||||
return {
|
||||
delete: (id, postId) => dispatch(commentActions.dbDeleteComment(id, postId)),
|
||||
update: (id, postId, comment) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
|
||||
delete: (id: string| null, postId: string) => dispatch(commentActions.dbDeleteComment(id, postId)),
|
||||
update: (id: string, postId: string, comment: string) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
|
||||
openEditor: () => dispatch(commentActions.openCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
|
||||
closeEditor: () => dispatch(commentActions.closeCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
|
||||
getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId))
|
||||
getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId,''))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +318,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state: any, ownProps: any) => {
|
||||
const {uid} = state.authorize
|
||||
const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : ''
|
||||
const fullName = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].fullName || '' : ''
|
||||
@@ -313,10 +328,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
info: state.user.info,
|
||||
avatar,
|
||||
fullName
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Comment)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentComponent as any)
|
||||
98
src/components/comment/ICommentComponentProps.ts
Normal file
98
src/components/comment/ICommentComponentProps.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { Profile } from 'core/domain/users'
|
||||
export interface ICommentComponentProps {
|
||||
|
||||
/**
|
||||
* Comment
|
||||
*
|
||||
* @type {Comment}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
comment: Comment
|
||||
|
||||
/**
|
||||
* Open profile editor
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
openEditor: Function
|
||||
|
||||
/**
|
||||
* Close comment editor
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
closeEditor: () => any
|
||||
|
||||
/**
|
||||
* Current user is comment owner {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
isCommentOwner: boolean
|
||||
|
||||
/**
|
||||
* Current user is post owner {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* Update comment
|
||||
*
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
update: (id?: string | null, postId?: string, comment?: string | null) => any
|
||||
|
||||
/**
|
||||
* Delete comment
|
||||
*
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
delete: (id?: string| null, postId?: string) => any
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
*
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
getUserInfo: () => void
|
||||
|
||||
/**
|
||||
* User profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
info: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
fullName: string
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Comment
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* Writing comment on the post is disabled {true} or not false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
|
||||
}
|
||||
43
src/components/comment/ICommentComponentState.ts
Normal file
43
src/components/comment/ICommentComponentState.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
export interface ICommentComponentState {
|
||||
|
||||
/**
|
||||
* Initialt text comment
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
initialText?: string | null
|
||||
|
||||
/**
|
||||
* Initialt text comment
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
text?: string | null
|
||||
|
||||
/**
|
||||
* Comment is in edit state {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentState
|
||||
*/
|
||||
editDisabled: boolean
|
||||
|
||||
/**
|
||||
* Current user is the post owner {true} or not falses
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentState
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* Display comment {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentState
|
||||
*/
|
||||
display?: boolean
|
||||
}
|
||||
2
src/components/comment/index.ts
Normal file
2
src/components/comment/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CommentComponent from './CommentComponent'
|
||||
export default CommentComponent
|
||||
@@ -10,60 +10,76 @@ import { ListItem } from 'material-ui/List'
|
||||
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
|
||||
|
||||
// - Import actions
|
||||
import * as commentActions from 'commentActions'
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
|
||||
// - Import app components
|
||||
import CommentList from 'CommentList'
|
||||
import CommentWrite from 'CommentWrite'
|
||||
import UserAvatar from 'UserAvatar'
|
||||
import CommentListComponent from 'components/CommentList'
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
|
||||
import { ICommentGroupComponentProps } from './ICommentGroupComponentProps'
|
||||
import { ICommentGroupComponentState } from './ICommentGroupComponentState'
|
||||
import { Comment } from 'core/domain/comments/comment'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CommentGroup extends Component {
|
||||
export class CommentGroupComponent extends Component<ICommentGroupComponentProps, ICommentGroupComponentState> {
|
||||
|
||||
|
||||
static propTypes = {
|
||||
static propTypes = {
|
||||
/**
|
||||
* If it's true comment box will be open
|
||||
*/
|
||||
open: PropTypes.bool,
|
||||
open: PropTypes.bool,
|
||||
/**
|
||||
* If it's true the comment is disable to write
|
||||
*/
|
||||
disableComments: PropTypes.bool,
|
||||
disableComments: PropTypes.bool,
|
||||
/**
|
||||
* The post identifier which comment belong to
|
||||
*/
|
||||
postId: PropTypes.string,
|
||||
postId: PropTypes.string,
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
isPostOwner: PropTypes.bool,
|
||||
isPostOwner: PropTypes.bool,
|
||||
/**
|
||||
* Toggle on show/hide comment by passing callback from parent component
|
||||
*/
|
||||
onToggleRequest: PropTypes.func,
|
||||
onToggleRequest: PropTypes.func,
|
||||
/**
|
||||
* The user identifier of the post owner which comment belong to
|
||||
* The user identifier of the post owner which comment belong to
|
||||
*/
|
||||
ownerPostUserId:PropTypes.string
|
||||
ownerPostUserId: PropTypes.string
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
styles = {
|
||||
commentItem: {
|
||||
height: '60px',
|
||||
position: '',
|
||||
zIndex: ''
|
||||
},
|
||||
toggleShowList: {
|
||||
height: '60px',
|
||||
zIndex: 5
|
||||
},
|
||||
writeCommentTextField: {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: ICommentGroupComponentProps) {
|
||||
super(props)
|
||||
|
||||
/**
|
||||
* Defaul state
|
||||
*/
|
||||
this.state = {
|
||||
commentText: "",
|
||||
commentText: '',
|
||||
postDisable: true
|
||||
|
||||
}
|
||||
@@ -72,8 +88,6 @@ static propTypes = {
|
||||
this.commentList = this.commentList.bind(this)
|
||||
this.handlePostComment = this.handlePostComment.bind(this)
|
||||
this.clearCommentWrite = this.clearCommentWrite.bind(this)
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +104,7 @@ static propTypes = {
|
||||
* Post comment
|
||||
*/
|
||||
handlePostComment = () => {
|
||||
this.props.send(this.state.commentText, this.props.postId, this.clearCommentWrite)
|
||||
this.props.send!(this.state.commentText, this.props.postId, this.clearCommentWrite)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,15 +112,14 @@ static propTypes = {
|
||||
* @param {event} evt is an event passed by change comment text callback funciton
|
||||
* @param {string} data is the comment text which user writes
|
||||
*/
|
||||
handleOnChange = (evt, data) => {
|
||||
handleOnChange = (evt: any, data: any) => {
|
||||
this.setState({ commentText: data })
|
||||
if (data.length === 0 || data.trim() === '') {
|
||||
this.setState({
|
||||
commentText: '',
|
||||
postDisable: true
|
||||
})
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.setState({
|
||||
commentText: data,
|
||||
postDisable: false
|
||||
@@ -123,48 +136,45 @@ static propTypes = {
|
||||
let comments = this.props.comments
|
||||
if (comments) {
|
||||
|
||||
|
||||
let parsedComments = []
|
||||
let parsedComments: Comment[] = []
|
||||
Object.keys(comments).slice(0, 3).forEach((commentId) => {
|
||||
parsedComments.push({
|
||||
id: commentId,
|
||||
...comments[commentId]
|
||||
...comments![commentId]
|
||||
})
|
||||
})
|
||||
if (parsedComments.length === 2) {
|
||||
parsedComments.push(parsedComments[0])
|
||||
}
|
||||
else if (parsedComments.length === 1) {
|
||||
} else if (parsedComments.length === 1) {
|
||||
parsedComments.push(parsedComments[0])
|
||||
parsedComments.push(parsedComments[0])
|
||||
}
|
||||
return parsedComments.map((comment, index) => {
|
||||
const {userInfo} = this.props
|
||||
|
||||
const commentAvatar = userInfo && userInfo[comment.userId] ? userInfo[comment.userId].avatar || '' : ''
|
||||
const commentFullName = userInfo && userInfo[comment.userId] ? userInfo[comment.userId].fullName || '' : ''
|
||||
|
||||
return (<ListItem key={index} style={{ height: "60px", position: "", zIndex: "" }} innerDivStyle={{ padding: "6px 16px 16px 72px" }}
|
||||
leftAvatar={<UserAvatar fullName={commentFullName} fileName={commentAvatar} style={{ top: "8px" }} size={36} />}
|
||||
secondaryText={<div style={{ height: "" }}>
|
||||
const commentAvatar = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].avatar || '' : ''
|
||||
const commentFullName = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].fullName || '' : ''
|
||||
|
||||
return (<ListItem key={index} style={this.styles.commentItem as any} innerDivStyle={{ padding: '6px 16px 16px 72px' }}
|
||||
leftAvatar={<UserAvatarComponent fullName={commentFullName} fileName={commentAvatar} style={{ top: '8px' }} size={36} />}
|
||||
secondaryText={<div style={{ height: '' }}>
|
||||
<span style={{
|
||||
fontSize: "13px",
|
||||
paddingRight: "10px",
|
||||
fontSize: '13px',
|
||||
paddingRight: '10px',
|
||||
fontWeight: 400,
|
||||
color: "rgba(0,0,0,0.87)",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden"
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{comment.userDisplayName}:
|
||||
</span>
|
||||
<span style={{
|
||||
fontSize: "13px",
|
||||
lineHeight: "20px",
|
||||
color: "rgba(0,0,0,0.87)",
|
||||
fontSize: '13px',
|
||||
lineHeight: '20px',
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
fontWeight: 300,
|
||||
whiteSpace: "pre-wrap"
|
||||
whiteSpace: 'pre-wrap'
|
||||
}}>{comment.text}</span>
|
||||
|
||||
</div>}
|
||||
secondaryTextLines={2}
|
||||
/>
|
||||
@@ -180,55 +190,53 @@ static propTypes = {
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: "block" } : { display: "none" }}>
|
||||
<div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: 'block' } : { display: 'none' }}>
|
||||
<Divider />
|
||||
<Paper zDepth={0} className="animate-top" style={!this.props.open ? { display: "block" } : { display: "none" }}>
|
||||
<Paper zDepth={0} className='animate-top' style={!this.props.open ? { display: 'block' } : { display: 'none' }}>
|
||||
|
||||
<div style={{ position: "relative", height: "60px" }} >
|
||||
<FlatButton label=" " style={{ height: "60px", zIndex: 5 }} fullWidth={true} onClick={this.props.onToggleRequest} />
|
||||
<div style={{ position: 'relative', height: '60px' }} >
|
||||
<FlatButton label=' ' style={this.styles.toggleShowList} fullWidth={true} onClick={this.props.onToggleRequest} />
|
||||
|
||||
<div className="comment__list-show">
|
||||
<div className='comment__list-show'>
|
||||
{this.commentList()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
{(this.props.comments && Object.keys(this.props.comments).length > 0)
|
||||
? (<Paper zDepth={0} style={this.props.open ? { display: "block", padding: "0px 0px" } : { display: "none", padding: "12px 16px" }}>
|
||||
<CommentList comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
? (<Paper zDepth={0} style={this.props.open ? { display: 'block', padding: '0px 0px' } : { display: 'none', padding: '12px 16px' }}>
|
||||
<CommentListComponent comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
|
||||
</Paper>) : ''}
|
||||
|
||||
</div>
|
||||
{!this.props.disableComments ? (<div>
|
||||
<Divider />
|
||||
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", overflowY: "auto", padding: "12px 16px", display: (this.props.open ? "block" : "none") }}>
|
||||
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', overflowY: 'auto', padding: '12px 16px', display: (this.props.open ? 'block' : 'none') }}>
|
||||
|
||||
<div style={{ display: "flex" }}>
|
||||
<UserAvatar fullName={this.props.fullName} fileName={this.props.avatar} style={{ flex: "none", margin: "4px 0px" }} size={36} />
|
||||
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<UserAvatarComponent fullName={this.props.fullName!} fileName={this.props.avatar!} style={{ flex: 'none', margin: '4px 0px' }} size={36} />
|
||||
<div style={{ outline: 'none', marginLeft: '16px', flex: 'auto', flexGrow: 1 }}>
|
||||
<TextField
|
||||
value={this.state.commentText}
|
||||
onChange={this.handleOnChange}
|
||||
hintText="Add a comment..."
|
||||
hintText='Add a comment...'
|
||||
underlineShow={false}
|
||||
multiLine={true}
|
||||
rows={1}
|
||||
hintStyle={{ fontWeight: 100, fontSize: "14px" }}
|
||||
hintStyle={{ fontWeight: 100, fontSize: '14px' }}
|
||||
rowsMax={4}
|
||||
textareaStyle={{ fontWeight: 100, fontSize: "14px" }}
|
||||
style={{ width: '100%' }}
|
||||
textareaStyle={{ fontWeight: 100, fontSize: '14px' }}
|
||||
style={this.styles.writeCommentTextField}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FlatButton primary={true} disabled={this.state.postDisable} label="Post" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handlePostComment} />
|
||||
<FlatButton primary={true} disabled={this.state.postDisable} label='Post' style={{ float: 'right', clear: 'both', zIndex: 5, margin: '0px 5px 5px 0px', fontWeight: 400 }} onClick={this.handlePostComment} />
|
||||
</Paper>
|
||||
</div>): ''}
|
||||
</div>) : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -240,9 +248,9 @@ static propTypes = {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps) => {
|
||||
return {
|
||||
send: (text, postId, callBack) => {
|
||||
send: (text: string, postId: string, callBack: Function) => {
|
||||
dispatch(commentActions.dbAddComment(ownProps.ownerPostUserId,{
|
||||
postId: postId,
|
||||
text: text
|
||||
@@ -257,15 +265,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => {
|
||||
return {
|
||||
comments: state.comment.postComments[ownProps.postId],
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '',
|
||||
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName || '' : '',
|
||||
userInfo: state.user.info
|
||||
userInfo: state.user.info
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentGroup)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentGroupComponent as any)
|
||||
93
src/components/commentGroup/ICommentGroupComponentProps.ts
Normal file
93
src/components/commentGroup/ICommentGroupComponentProps.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
export interface ICommentGroupComponentProps {
|
||||
|
||||
/**
|
||||
* Commnets
|
||||
*
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
comments?: {[commentId: string]: Comment}
|
||||
|
||||
/**
|
||||
* The post identifier which comment belong to
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
postId: string
|
||||
|
||||
/**
|
||||
* Users` profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
userInfo?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* Comment group is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Comment is disabled {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
|
||||
/**
|
||||
* Current user is the post owner {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
fullName?: string
|
||||
|
||||
/**
|
||||
* Avatar URL address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
avatar?: string
|
||||
|
||||
/**
|
||||
* Toggle comment list open/close
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
onToggleRequest: () => void
|
||||
|
||||
/**
|
||||
* The identifier of post owner
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
ownerPostUserId: string
|
||||
|
||||
/**
|
||||
* Send comment
|
||||
*
|
||||
* @type {(commentText: string, postId: string, clearCommentWrite: Function)}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
send?: (commentText: string, postId: string, clearCommentWrite: () => void) => any
|
||||
|
||||
}
|
||||
19
src/components/commentGroup/ICommentGroupComponentState.ts
Normal file
19
src/components/commentGroup/ICommentGroupComponentState.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
export interface ICommentGroupComponentState {
|
||||
|
||||
/**
|
||||
* Comment text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentState
|
||||
*/
|
||||
commentText: string
|
||||
|
||||
/**
|
||||
* Disable post comment
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentState
|
||||
*/
|
||||
postDisable: boolean
|
||||
}
|
||||
2
src/components/commentGroup/index.ts
Normal file
2
src/components/commentGroup/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CommentGroupComponent from './CommentGroupComponent'
|
||||
export default CommentGroupComponent
|
||||
@@ -4,24 +4,23 @@ import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
|
||||
|
||||
|
||||
|
||||
// - Import app components
|
||||
import Comment from 'Comment'
|
||||
import * as PostAPI from 'PostAPI'
|
||||
import CommentComponent from 'components/Comment'
|
||||
|
||||
import * as PostAPI from 'api/PostAPI'
|
||||
|
||||
import { ICommentListComponentProps } from './ICommentListComponentProps'
|
||||
import { ICommentListComponentState } from './ICommentListComponentState'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
|
||||
// - Import actions
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CommentList extends Component {
|
||||
export class CommentListComponent extends Component<ICommentListComponentProps, ICommentListComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
static propTypes = {
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
@@ -29,14 +28,14 @@ export class CommentList extends Component {
|
||||
/**
|
||||
* If it's true the comment is disable to write
|
||||
*/
|
||||
disableComments: PropTypes.bool,
|
||||
}
|
||||
disableComments: PropTypes.bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: ICommentListComponentProps) {
|
||||
super(props)
|
||||
|
||||
/**
|
||||
@@ -50,8 +49,6 @@ export class CommentList extends Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get comments' DOM
|
||||
* @return {DOM} list of comments' DOM
|
||||
@@ -60,8 +57,7 @@ export class CommentList extends Component {
|
||||
let comments = this.props.comments
|
||||
if (comments) {
|
||||
|
||||
|
||||
let parsedComments = []
|
||||
let parsedComments: Comment[] = []
|
||||
Object.keys(comments).forEach((commentId) => {
|
||||
parsedComments.push({
|
||||
id: commentId,
|
||||
@@ -69,10 +65,10 @@ export class CommentList extends Component {
|
||||
})
|
||||
})
|
||||
let sortedComments = PostAPI.sortObjectsDate(parsedComments)
|
||||
|
||||
return sortedComments.map((comment, index, array) => {
|
||||
|
||||
return <Comment key={comment.id} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
return sortedComments.map((comment: Comment, index: number, array: Comment) => {
|
||||
|
||||
return <CommentComponent key={comment.id} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
|
||||
})
|
||||
|
||||
@@ -83,11 +79,11 @@ export class CommentList extends Component {
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
render () {
|
||||
|
||||
const styles = {
|
||||
const styles: any = {
|
||||
list: {
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
maxHeight: 450,
|
||||
overflowY: 'auto'
|
||||
}
|
||||
@@ -95,7 +91,6 @@ export class CommentList extends Component {
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<List style={styles.list}>
|
||||
|
||||
{this.commentList()}
|
||||
@@ -110,10 +105,9 @@ export class CommentList extends Component {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ICommentListComponentProps) => {
|
||||
return {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,11 +117,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentList)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentListComponent as any)
|
||||
28
src/components/commentList/ICommentListComponentProps.ts
Normal file
28
src/components/commentList/ICommentListComponentProps.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Comment } from 'core/domain/comments'
|
||||
|
||||
export interface ICommentListComponentProps {
|
||||
|
||||
/**
|
||||
* Ad dictionary of comment
|
||||
*
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
comments: {[commentId: string]: Comment}
|
||||
|
||||
/**
|
||||
* Current user is post the post owner {true} or not false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* Comment on the post is disabled {false} or not {true}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
}
|
||||
4
src/components/commentList/ICommentListComponentState.ts
Normal file
4
src/components/commentList/ICommentListComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface ICommentListComponentState {
|
||||
|
||||
}
|
||||
2
src/components/commentList/index.ts
Normal file
2
src/components/commentList/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CommentListComponent from './CommentListComponent'
|
||||
export default CommentListComponent
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
@@ -18,316 +16,296 @@ import Divider from 'material-ui/Divider'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import TextField from 'material-ui/TextField'
|
||||
|
||||
|
||||
// - Import app components
|
||||
import ImgCover from 'ImgCover'
|
||||
import DialogTitle from 'DialogTitle'
|
||||
import ImageGallery from 'ImageGallery'
|
||||
import FileAPI from 'FileAPI'
|
||||
import UserAvatar from 'UserAvatar'
|
||||
|
||||
|
||||
|
||||
import ImgCover from 'components/imgCover'
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
import ImageGallery from 'components/imageGallery'
|
||||
import DialogTitle from 'layouts/DialogTitle'
|
||||
|
||||
// - Import API
|
||||
|
||||
import FileAPI from 'api/FileAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'userActions'
|
||||
import * as globalActions from 'globalActions'
|
||||
import * as imageGalleryActions from 'imageGalleryActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
|
||||
import { IEditProfileComponentProps } from './IEditProfileComponentProps'
|
||||
import { IEditProfileComponentState } from './IEditProfileComponentState'
|
||||
import { Profile } from 'core/domain/users'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
* Create component class
|
||||
*/
|
||||
export class EditProfile extends Component {
|
||||
export class EditProfileComponent extends Component<IEditProfileComponentProps,IEditProfileComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*/
|
||||
avatar: PropTypes.string,
|
||||
avatar: PropTypes.string,
|
||||
/**
|
||||
* User avatar address
|
||||
*/
|
||||
banner: PropTypes.string,
|
||||
banner: PropTypes.string,
|
||||
/**
|
||||
* User full name
|
||||
*/
|
||||
fullName: PropTypes.string.isRequired
|
||||
fullName: PropTypes.string.isRequired
|
||||
|
||||
}
|
||||
|
||||
styles = {
|
||||
avatar: {
|
||||
border: '2px solid rgb(255, 255, 255)'
|
||||
},
|
||||
paper: {
|
||||
width: '90%',
|
||||
height: '100%',
|
||||
margin: '0 auto',
|
||||
display: 'block'
|
||||
},
|
||||
title: {
|
||||
padding: '24px 24px 20px 24px',
|
||||
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
|
||||
display: 'flex',
|
||||
wordWrap: 'break-word',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
flexGrow: 1
|
||||
},
|
||||
actions: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '24px 24px 20px'
|
||||
},
|
||||
updateButton: {
|
||||
marginLeft: '10px'
|
||||
},
|
||||
box: {
|
||||
padding: '0px 24px 20px 24px',
|
||||
display: 'flex'
|
||||
|
||||
},
|
||||
dialogGallery: {
|
||||
width: '',
|
||||
maxWidth: '530px',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
iconButtonSmall: {
|
||||
},
|
||||
iconButton: {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
//Defaul state
|
||||
this.state = {
|
||||
constructor (props: IEditProfileComponentProps) {
|
||||
super(props)
|
||||
// Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* If it's true the winow is in small size
|
||||
*/
|
||||
isSmall: false,
|
||||
isSmall: false,
|
||||
/**
|
||||
* User tag line input value
|
||||
*/
|
||||
tagLineInput: props.info.tagLine || '',
|
||||
tagLineInput: props.info!.tagLine || '',
|
||||
/**
|
||||
* User full name input value
|
||||
*/
|
||||
fullNameInput: props.info.fullName || '',
|
||||
fullNameInput: props.info!.fullName || '',
|
||||
/**
|
||||
* Error message of full name input
|
||||
*/
|
||||
fullNameInputError: '',
|
||||
fullNameInputError: '',
|
||||
/**
|
||||
* User banner address
|
||||
*/
|
||||
banner: this.props.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
|
||||
banner: props.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
|
||||
/**
|
||||
* User avatar address
|
||||
*/
|
||||
avatar: this.props.avatar || '',
|
||||
avatar: props.avatar || '',
|
||||
/**
|
||||
* It's true if the image galley for banner is open
|
||||
*/
|
||||
openBanner: false,
|
||||
openBanner: false,
|
||||
/**
|
||||
* It's true if the image gallery for avatar is open
|
||||
*/
|
||||
openAvatar: false
|
||||
openAvatar: false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleChangeDate = this.handleChangeDate.bind(this)
|
||||
this.handleUpdate = this.handleUpdate.bind(this)
|
||||
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
|
||||
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
|
||||
this.handleUpdate = this.handleUpdate.bind(this)
|
||||
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
|
||||
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Close image gallery of banner
|
||||
*/
|
||||
handleCloseBannerGallery = () => {
|
||||
this.setState({
|
||||
openBanner: false
|
||||
})
|
||||
}
|
||||
handleCloseBannerGallery = () => {
|
||||
this.setState({
|
||||
openBanner: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Open image gallery of banner
|
||||
*/
|
||||
handleOpenBannerGallery = () => {
|
||||
this.setState({
|
||||
openBanner: true
|
||||
})
|
||||
}
|
||||
handleOpenBannerGallery = () => {
|
||||
this.setState({
|
||||
openBanner: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Close image gallery of avatar
|
||||
*/
|
||||
handleCloseAvatarGallery = () => {
|
||||
this.setState({
|
||||
openAvatar: false
|
||||
})
|
||||
}
|
||||
handleCloseAvatarGallery = () => {
|
||||
this.setState({
|
||||
openAvatar: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open image gallery of avatar
|
||||
*/
|
||||
handleOpenAvatarGallery = () => {
|
||||
this.setState({
|
||||
openAvatar: true
|
||||
})
|
||||
}
|
||||
handleOpenAvatarGallery = () => {
|
||||
this.setState({
|
||||
openAvatar: true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Set banner image url
|
||||
*/
|
||||
handleRequestSetBanner = (url) => {
|
||||
console.log('==========Banner==================')
|
||||
console.log(url)
|
||||
console.log('====================================')
|
||||
this.setState({
|
||||
banner: url
|
||||
})
|
||||
}
|
||||
handleRequestSetBanner = (url: string) => {
|
||||
this.setState({
|
||||
banner: url
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Set avatar image url
|
||||
*/
|
||||
handleRequestSetAvatar = (fileName) => {
|
||||
this.setState({
|
||||
avatar: fileName
|
||||
})
|
||||
}
|
||||
handleRequestSetAvatar = (fileName: string) => {
|
||||
this.setState({
|
||||
avatar: fileName
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update profile on the server
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @memberof EditProfile
|
||||
*/
|
||||
handleUpdate = () => {
|
||||
const {fullNameInput, tagLineInput, avatar, banner} = this.state
|
||||
|
||||
if (this.state.fullNameInput.trim() === '') {
|
||||
this.setState({
|
||||
fullNameInputError: 'This field is required'
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
fullNameInputError: ''
|
||||
})
|
||||
handleUpdate = () => {
|
||||
const {fullNameInput, tagLineInput, avatar, banner} = this.state
|
||||
|
||||
this.props.update({
|
||||
fullName: fullNameInput,
|
||||
tagLine: tagLineInput,
|
||||
avatar: avatar,
|
||||
banner: banner
|
||||
})
|
||||
}
|
||||
if (this.state.fullNameInput.trim() === '') {
|
||||
this.setState({
|
||||
fullNameInputError: 'This field is required'
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
fullNameInputError: ''
|
||||
})
|
||||
|
||||
this.props.update!({
|
||||
fullName: fullNameInput,
|
||||
tagLine: tagLineInput,
|
||||
avatar: avatar,
|
||||
banner: banner
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data on input change
|
||||
* @param {event} evt is an event of inputs of element on change
|
||||
*/
|
||||
handleInputChange = (evt) => {
|
||||
const target = evt.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
[name]: value
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Handle change date
|
||||
*/
|
||||
handleChangeDate = (evt, date) => {
|
||||
this.setState({
|
||||
birthdayInput: date,
|
||||
})
|
||||
}
|
||||
handleInputChange = (event: any) => {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
[name]: value
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resize event for window to change sidebar status
|
||||
* @param {event} evt is the event is passed by winodw resize event
|
||||
*/
|
||||
handleResize = (evt) => {
|
||||
/**
|
||||
* Handle resize event for window to change sidebar status
|
||||
* @param {any} event is the event is passed by winodw resize event
|
||||
*/
|
||||
handleResize = (event: any) => {
|
||||
|
||||
// Set initial state
|
||||
let width = window.innerWidth
|
||||
let width = window.innerWidth
|
||||
|
||||
if (width > 900) {
|
||||
this.setState({
|
||||
isSmall: false
|
||||
})
|
||||
if (width > 900) {
|
||||
this.setState({
|
||||
isSmall: false
|
||||
})
|
||||
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
isSmall: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentDidMount = () => {
|
||||
this.handleResize()
|
||||
} else {
|
||||
this.setState({
|
||||
isSmall: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.handleResize(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
render () {
|
||||
|
||||
const styles = {
|
||||
avatar: {
|
||||
border: '2px solid rgb(255, 255, 255)'
|
||||
},
|
||||
paper: {
|
||||
width: '90%',
|
||||
height: '100%',
|
||||
margin: '0 auto',
|
||||
display: 'block',
|
||||
},
|
||||
title: {
|
||||
padding: '24px 24px 20px 24px',
|
||||
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
|
||||
display: 'flex',
|
||||
wordWrap: 'break-word',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
flexGrow: 1
|
||||
},
|
||||
actions: {
|
||||
display: 'flex',
|
||||
justifyContent: "flex-end",
|
||||
padding: '24px 24px 20px'
|
||||
},
|
||||
updateButton: {
|
||||
marginLeft: '10px'
|
||||
},
|
||||
box: {
|
||||
padding: '0px 24px 20px 24px',
|
||||
display: 'flex'
|
||||
|
||||
},
|
||||
dialogGallery: {
|
||||
width: '',
|
||||
maxWidth: '530px',
|
||||
borderRadius: "4px"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const iconButtonElement = (
|
||||
<IconButton style={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton} iconStyle={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton}
|
||||
const iconButtonElement = (
|
||||
<IconButton style={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton} iconStyle={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton}
|
||||
touch={true}
|
||||
>
|
||||
<MoreVertIcon color={grey400} viewBox='10 0 24 24' />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
|
||||
const RightIconMenu = () => (
|
||||
const RightIconMenu = () => (
|
||||
<IconMenu iconButtonElement={iconButtonElement}>
|
||||
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
|
||||
<MenuItem style={{ fontSize: "14px" }}>Edit</MenuItem>
|
||||
<MenuItem style={{ fontSize: "14px" }}>Delete</MenuItem>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Edit</MenuItem>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Delete</MenuItem>
|
||||
</IconMenu>
|
||||
)
|
||||
|
||||
|
||||
|
||||
return (
|
||||
return (
|
||||
|
||||
<div>
|
||||
{/* Edit profile dialog */}
|
||||
<Dialog
|
||||
id='Edit-Profile'
|
||||
key='Edit-Profile'
|
||||
modal={false}
|
||||
open={this.props.open}
|
||||
open={this.props.open!}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
autoScrollBodyContent={true}
|
||||
bodyStyle={{ backgroundColor: "none", padding: 'none', borderTop: 'none', borderBottom: 'none' }}
|
||||
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
|
||||
contentStyle={{ backgroundColor: "none", maxWidth: '450px', maxHeight: 'none', height: '100%' }}
|
||||
style={{ backgroundColor: "none", maxHeight: 'none', height: '100%' }}
|
||||
bodyStyle={{ backgroundColor: 'none', padding: 'none', borderTop: 'none', borderBottom: 'none' }}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
contentStyle={{ backgroundColor: 'none', maxWidth: '450px', maxHeight: 'none', height: '100%' }}
|
||||
style={{ backgroundColor: 'none', maxHeight: 'none', height: '100%' }}
|
||||
>
|
||||
{/* Banner */}
|
||||
<div style={{ position: 'relative' }}>
|
||||
@@ -338,7 +316,7 @@ export class EditProfile extends Component {
|
||||
</div>
|
||||
<div className='profile__edit'>
|
||||
<EventListener
|
||||
target="window"
|
||||
target='window'
|
||||
onResize={this.handleResize}
|
||||
/>
|
||||
<div className='left'>
|
||||
@@ -348,25 +326,24 @@ export class EditProfile extends Component {
|
||||
<SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} />
|
||||
|
||||
</div>
|
||||
<UserAvatar fullName={(this.props.info ? this.props.info.fullName : '')} fileName={this.state.avatar} size={90} style={styles.avatar} />
|
||||
<UserAvatarComponent fullName={(this.props.info ? this.props.info.fullName : '')} fileName={this.state.avatar} size={90} style={this.styles.avatar} />
|
||||
</div>
|
||||
<div className='info'>
|
||||
<div className='fullName'>
|
||||
{this.props.fullName}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Edit user information box*/}
|
||||
<Paper style={styles.paper} zDepth={1}>
|
||||
<div style={styles.title}>Personal Information</div>
|
||||
<div style={styles.box}>
|
||||
<Paper style={this.styles.paper} zDepth={1}>
|
||||
<div style={this.styles.title as any}>Personal Information</div>
|
||||
<div style={this.styles.box}>
|
||||
<TextField
|
||||
floatingLabelText="Full name"
|
||||
floatingLabelText='Full name'
|
||||
onChange={this.handleInputChange}
|
||||
name='fullNameInput'
|
||||
errorText={this.state.fullNameInputError}
|
||||
@@ -374,23 +351,22 @@ export class EditProfile extends Component {
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div style={styles.box}>
|
||||
<div style={this.styles.box}>
|
||||
<TextField
|
||||
floatingLabelText="Tag Line"
|
||||
floatingLabelText='Tag Line'
|
||||
onChange={this.handleInputChange}
|
||||
name='tagLineInput'
|
||||
value={this.state.tagLineInput}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div style={styles.actions}>
|
||||
<FlatButton label="CANCEL" onClick={this.props.onRequestClose} />
|
||||
<RaisedButton label="UPDATE" primary={true} onClick={this.handleUpdate} style={styles.updateButton} />
|
||||
<div style={this.styles.actions as any}>
|
||||
<FlatButton label='CANCEL' onClick={this.props.onRequestClose} />
|
||||
<RaisedButton label='UPDATE' primary={true} onClick={this.handleUpdate} style={this.styles.updateButton} />
|
||||
</div>
|
||||
</Paper>
|
||||
<div style={{ height: '16px' }}></div>
|
||||
|
||||
|
||||
</Dialog>
|
||||
|
||||
{/* Image gallery for banner*/}
|
||||
@@ -398,9 +374,9 @@ export class EditProfile extends Component {
|
||||
title={<DialogTitle title='Choose an banner image' onRequestClose={this.handleCloseBannerGallery} />}
|
||||
modal={false}
|
||||
open={this.state.openBanner}
|
||||
contentStyle={styles.dialogGallery}
|
||||
contentStyle={this.styles.dialogGallery}
|
||||
onRequestClose={this.handleCloseBannerGallery}
|
||||
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
autoDetectWindowHeight={false}
|
||||
|
||||
>
|
||||
@@ -412,9 +388,9 @@ export class EditProfile extends Component {
|
||||
title={<DialogTitle title='Choose an avatar image' onRequestClose={this.handleCloseAvatarGallery} />}
|
||||
modal={false}
|
||||
open={this.state.openAvatar}
|
||||
contentStyle={styles.dialogGallery}
|
||||
contentStyle={this.styles.dialogGallery}
|
||||
onRequestClose={this.handleCloseAvatarGallery}
|
||||
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
autoDetectWindowHeight={false}
|
||||
|
||||
>
|
||||
@@ -422,23 +398,22 @@ export class EditProfile extends Component {
|
||||
</Dialog>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
update: (info) => dispatch(userActions.dbUpdateUserInfo(info)),
|
||||
onRequestClose: () => dispatch(userActions.closeEditProfile())
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IEditProfileComponentProps) => {
|
||||
return {
|
||||
update: (info: Profile) => dispatch(userActions.dbUpdateUserInfo(info)),
|
||||
onRequestClose: () => dispatch(userActions.closeEditProfile())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,14 +422,14 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
open: state.user.openEditProfile,
|
||||
info: state.user.info[state.authorize.uid],
|
||||
avatarURL: state.imageGallery.imageURLList
|
||||
const mapStateToProps = (state: any, ownProps: IEditProfileComponentProps) => {
|
||||
return {
|
||||
open: state.user.openEditProfile,
|
||||
info: state.user.info[state.authorize.uid],
|
||||
avatarURL: state.imageGallery.imageURLList
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EditProfile)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EditProfileComponent as any)
|
||||
58
src/components/editProfile/IEditProfileComponentProps.ts
Normal file
58
src/components/editProfile/IEditProfileComponentProps.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
|
||||
export interface IEditProfileComponentProps {
|
||||
|
||||
/**
|
||||
* User profile
|
||||
*
|
||||
* @type {Profile}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
info?: Profile
|
||||
|
||||
/**
|
||||
* User profile banner addresss
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
banner: string
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
fullName: string
|
||||
|
||||
/**
|
||||
* Edit profile dialog is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
open?: boolean
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
update?: (profile: Profile) => void
|
||||
|
||||
/**
|
||||
* On edit profile dialog close event
|
||||
*
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
onRequestClose?: () => void
|
||||
}
|
||||
68
src/components/editProfile/IEditProfileComponentState.ts
Normal file
68
src/components/editProfile/IEditProfileComponentState.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
export interface IEditProfileComponentState {
|
||||
|
||||
/**
|
||||
* Full name input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
fullNameInput: string
|
||||
|
||||
/**
|
||||
* Full name input error message
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
fullNameInputError: string
|
||||
|
||||
/**
|
||||
* Tag line input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
tagLineInput: string
|
||||
|
||||
/**
|
||||
* Edit profile page is small size {true} or big {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
isSmall: boolean
|
||||
|
||||
/**
|
||||
* User's banner URL
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
banner: string
|
||||
|
||||
/**
|
||||
* User's avatar URL address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* Image gallery dialog is open for choosing banner image {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
openBanner: boolean
|
||||
|
||||
/**
|
||||
* Image gallery dialog is open for choosing avatar image {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
openAvatar: boolean
|
||||
|
||||
}
|
||||
2
src/components/editProfile/index.ts
Normal file
2
src/components/editProfile/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import EditProfileComponent from './EditProfileComponent'
|
||||
export default EditProfileComponent
|
||||
@@ -4,70 +4,69 @@ import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import Paper from 'material-ui/Paper'
|
||||
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'UserBoxList'
|
||||
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
|
||||
// - Import API
|
||||
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'userActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
import { IFindPeopleComponentProps } from './IFindPeopleComponentProps'
|
||||
import { IFindPeopleComponentState } from './IFindPeopleComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
* Create component class
|
||||
*/
|
||||
export class FindPeople extends Component {
|
||||
export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IFindPeopleComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props)
|
||||
constructor (props: IFindPeopleComponentProps) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
this.state = {
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.loadPeople()
|
||||
}
|
||||
componentWillMount () {
|
||||
this.props.loadPeople!()
|
||||
}
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
render () {
|
||||
|
||||
const styles = {
|
||||
paper: {
|
||||
height: 254,
|
||||
width: 243,
|
||||
margin: 10,
|
||||
textAlign: 'center',
|
||||
maxWidth: '257px'
|
||||
},
|
||||
followButton:{
|
||||
position: 'absolute',
|
||||
bottom: '8px',
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
}
|
||||
const styles = {
|
||||
paper: {
|
||||
height: 254,
|
||||
width: 243,
|
||||
margin: 10,
|
||||
textAlign: 'center',
|
||||
maxWidth: '257px'
|
||||
},
|
||||
followButton: {
|
||||
position: 'absolute',
|
||||
bottom: '8px',
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<div>
|
||||
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div>
|
||||
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div>
|
||||
<div className='profile__title'>
|
||||
Suggestions for you
|
||||
</div>
|
||||
@@ -77,21 +76,20 @@ export class FindPeople extends Component {
|
||||
Nothing to show! :(
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
return {
|
||||
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
|
||||
}
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps) => {
|
||||
return {
|
||||
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,11 +98,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
peopleInfo: state.user.info
|
||||
}
|
||||
const mapStateToProps = (state: any, ownProps: IFindPeopleComponentProps) => {
|
||||
return {
|
||||
peopleInfo: state.user.info
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FindPeople)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FindPeopleComponent as any)
|
||||
20
src/components/findPeople/IFindPeopleComponentProps.ts
Normal file
20
src/components/findPeople/IFindPeopleComponentProps.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Profile } from 'core/domain/users/profile'
|
||||
|
||||
export interface IFindPeopleComponentProps {
|
||||
|
||||
/**
|
||||
* Load users' profile
|
||||
*
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
loadPeople?: () => any
|
||||
|
||||
/**
|
||||
* Users' profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
peopleInfo?: {[userId: string]: Profile}
|
||||
|
||||
}
|
||||
4
src/components/findPeople/IFindPeopleComponentState.ts
Normal file
4
src/components/findPeople/IFindPeopleComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IFindPeopleComponentState {
|
||||
|
||||
}
|
||||
2
src/components/findPeople/index.ts
Normal file
2
src/components/findPeople/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import FindPeopleComponent from './FindPeopleComponent'
|
||||
export default FindPeopleComponent
|
||||
@@ -1,33 +1,35 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'UserBoxList'
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
|
||||
import { IFollowersComponentProps } from './IFollowersComponentProps'
|
||||
import { IFollowersComponentState } from './IFollowersComponentState'
|
||||
|
||||
// - Import API
|
||||
|
||||
|
||||
// - Import actions
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
* Create component class
|
||||
*/
|
||||
export class Followers extends Component {
|
||||
export class FollowersComponent extends Component<IFollowersComponentProps,IFollowersComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props){
|
||||
constructor (props: IFollowersComponentProps) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
@@ -40,24 +42,23 @@ static propTypes = {
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
{(this.props.followers && Object.keys(this.props.followers).length !==0) ? (<div>
|
||||
{(this.props.followers && Object.keys(this.props.followers).length !== 0) ? (<div>
|
||||
<div className='profile__title'>
|
||||
Followers
|
||||
</div>
|
||||
<UserBoxList users={this.props.followers} />
|
||||
<div style={{ height: '24px' }}></div>
|
||||
</div>)
|
||||
</div>)
|
||||
: (<div className='g__title-center'>
|
||||
No followers!
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
@@ -65,11 +66,11 @@ static propTypes = {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch,ownProps) => {
|
||||
return{
|
||||
const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
@@ -77,13 +78,13 @@ static propTypes = {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state,ownProps) => {
|
||||
const { uid } = state.authorize
|
||||
const circles = state.circle ? state.circle.userCircles[uid] : {}
|
||||
return{
|
||||
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
|
||||
}
|
||||
const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => {
|
||||
const { uid } = state.authorize
|
||||
const circles = state.circle ? state.circle.userCircles[uid] : {}
|
||||
return{
|
||||
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(Followers)
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(FollowersComponent as any)
|
||||
12
src/components/followers/IFollowersComponentProps.ts
Normal file
12
src/components/followers/IFollowersComponentProps.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { UserFollower } from 'core/domain/circles'
|
||||
|
||||
export interface IFollowersComponentProps {
|
||||
|
||||
/**
|
||||
* User followers info
|
||||
*
|
||||
* @type {{[userId: string]: UserFollower}}
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
followers?: {[userId: string]: UserFollower}
|
||||
}
|
||||
4
src/components/followers/IFollowersComponentState.ts
Normal file
4
src/components/followers/IFollowersComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IFollowersComponentState {
|
||||
|
||||
}
|
||||
2
src/components/followers/index.ts
Normal file
2
src/components/followers/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import FollowersComponent from './FollowersComponent'
|
||||
export default FollowersComponent
|
||||
@@ -1,34 +1,35 @@
|
||||
// - Import react components
|
||||
import React, {Component} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'UserBoxList'
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
|
||||
// - Import API
|
||||
import CircleAPI from 'CircleAPI'
|
||||
|
||||
import CircleAPI from 'api/CircleAPI'
|
||||
import { IFollowingComponentProps } from './IFollowingComponentProps'
|
||||
import { IFollowingComponentState } from './IFollowingComponentState'
|
||||
|
||||
// - Import actions
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
* Create component class
|
||||
*/
|
||||
export class Following extends Component {
|
||||
export class FollowingComponent extends Component<IFollowingComponentProps,IFollowingComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props){
|
||||
constructor (props: IFollowingComponentProps) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
@@ -41,25 +42,24 @@ static propTypes = {
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
|
||||
return (
|
||||
render () {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 )? (<div>
|
||||
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 ) ? (<div>
|
||||
<div className='profile__title'>
|
||||
Following
|
||||
</div>
|
||||
<UserBoxList users={this.props.followingUsers} />
|
||||
<div style={{ height: '24px' }}></div>
|
||||
|
||||
|
||||
</div>) : (<div className='g__title-center'>
|
||||
No following user!
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
@@ -67,11 +67,11 @@ static propTypes = {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch,ownProps) => {
|
||||
return{
|
||||
const mapDispatchToProps = (dispatch: any,ownProp: IFollowingComponentProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
@@ -79,17 +79,17 @@ static propTypes = {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state,ownProps) => {
|
||||
const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => {
|
||||
const { uid } = state.authorize
|
||||
const circles = state.circle ? state.circle.userCircles[uid] : {}
|
||||
const followingUsers = CircleAPI.getFollowingUsers(circles)
|
||||
return {
|
||||
uid,
|
||||
circles,
|
||||
followingUsers,
|
||||
|
||||
}
|
||||
followingUsers
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(Following)
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(FollowingComponent as any)
|
||||
6
src/components/following/IFollowingComponentProps.ts
Normal file
6
src/components/following/IFollowingComponentProps.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { UserFollower } from 'core/domain/circles'
|
||||
|
||||
export interface IFollowingComponentProps {
|
||||
|
||||
followingUsers?: {[userId: string]: UserFollower}
|
||||
}
|
||||
4
src/components/following/IFollowingComponentState.ts
Normal file
4
src/components/following/IFollowingComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IFollowingComponentState {
|
||||
|
||||
}
|
||||
2
src/components/following/index.ts
Normal file
2
src/components/following/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import FollowingComponent from './FollowingComponent'
|
||||
export default FollowingComponent
|
||||
@@ -15,34 +15,36 @@ import SvgAccountCircle from 'material-ui/svg-icons/action/account-circle'
|
||||
import SvgPeople from 'material-ui/svg-icons/social/people'
|
||||
|
||||
// - Import app components
|
||||
import Sidebar from 'Sidebar'
|
||||
import Blog from 'Blog'
|
||||
import HomeHeader from 'HomeHeader'
|
||||
import SidebarContent from 'SidebarContent'
|
||||
import SidebarMain from 'SidebarMain'
|
||||
import Profile from 'Profile'
|
||||
import PostPage from 'PostPage'
|
||||
import People from 'People'
|
||||
import Sidebar from 'components/sidebar'
|
||||
import StreamComponent from 'components/stream'
|
||||
import HomeHeader from 'components/homeHeader'
|
||||
import SidebarContent from 'components/sidebarContent'
|
||||
import SidebarMain from 'components/sidebarMain'
|
||||
import Profile from 'components/profile'
|
||||
import PostPage from 'components/postPage'
|
||||
import People from 'components/people'
|
||||
|
||||
// - Import API
|
||||
import CircleAPI from 'CircleAPI'
|
||||
import CircleAPI from 'api/CircleAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'globalActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
import { IHomeComponentProps } from './IHomeComponentProps'
|
||||
import { IHomeComponentState } from './IHomeComponentState'
|
||||
|
||||
// - Create Home component class
|
||||
export class Home extends Component {
|
||||
export class HomeComponent extends Component<IHomeComponentProps, IHomeComponentState> {
|
||||
|
||||
// Constructor
|
||||
constructor(props) {
|
||||
constructor (props: IHomeComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
this.state = {
|
||||
sidebarOpen: () => _,
|
||||
sidebarStatus: true,
|
||||
sidebaOverlay: false
|
||||
sidebarOverlay: false
|
||||
}
|
||||
|
||||
// Binding function to `this`
|
||||
@@ -57,37 +59,35 @@ export class Home extends Component {
|
||||
* handle close sidebar
|
||||
*/
|
||||
handleCloseSidebar = () => {
|
||||
this.state.sidebarOpen(false, 'overlay')
|
||||
this.state.sidebarOpen!(false, 'overlay')
|
||||
}
|
||||
|
||||
/**
|
||||
* Change sidebar overlay status
|
||||
* @param {boolean} status if is true, the sidebar is on overlay status
|
||||
*/
|
||||
sidebarOverlay = (status) => {
|
||||
sidebarOverlay = (status: boolean) => {
|
||||
this.setState({
|
||||
sidebarOverlay: status
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pass function to change sidebar status
|
||||
* Pass the function to change sidebar status
|
||||
* @param {boolean} open is a function callback to change sidebar status out of sidebar component
|
||||
*/
|
||||
sidebar = (open) => {
|
||||
sidebar = (open: (status: boolean, source: string) => void) => {
|
||||
|
||||
this.setState({
|
||||
sidebarOpen: open
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change sidebar status if is open or not
|
||||
* @param {boolean} status is true, if the sidebar is open
|
||||
*/
|
||||
sidebarStatus = (status) => {
|
||||
sidebarStatus = (status: boolean) => {
|
||||
this.setState({
|
||||
sidebarStatus: status
|
||||
})
|
||||
@@ -95,60 +95,59 @@ export class Home extends Component {
|
||||
|
||||
/**
|
||||
* Render DOM component
|
||||
*
|
||||
*
|
||||
* @returns DOM
|
||||
*
|
||||
*
|
||||
* @memberof Home
|
||||
*/
|
||||
render() {
|
||||
|
||||
render () {
|
||||
|
||||
return (
|
||||
<div id="home">
|
||||
<div id='home'>
|
||||
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
|
||||
<Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}>
|
||||
<SidebarContent>
|
||||
<Menu style={{ color: "rgb(117, 117, 117)", width: '210px' }}>
|
||||
<Menu style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
|
||||
{this.state.sidebarOverlay
|
||||
? <div><MenuItem onClick={this.handleCloseSidebar} primaryText={<span style={{ color: "rgb(117, 117, 117)" }} className="sidebar__title">Green</span>} rightIcon={<SvgArrowLeft viewBox="0 3 24 24" style={{ color: "#fff", marginLeft: "15px", width: "32px", height: "32px", cursor: "pointer" }} />} /><Divider /></div>
|
||||
: ""
|
||||
? <div><MenuItem onClick={this.handleCloseSidebar} primaryText={<span style={{ color: 'rgb(117, 117, 117)' }} className='sidebar__title'>Green</span>} rightIcon={<SvgArrowLeft viewBox='0 3 24 24' style={{ color: '#fff', marginLeft: '15px', width: '32px', height: '32px', cursor: 'pointer' }} />} /><Divider /></div>
|
||||
: ''
|
||||
}
|
||||
|
||||
<NavLink to='/'><MenuItem primaryText="Home" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgHome />} /></NavLink>
|
||||
<NavLink to={`/${this.props.uid}`}><MenuItem primaryText="Profile" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgAccountCircle />} /></NavLink>
|
||||
<NavLink to='/people'><MenuItem primaryText="People" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgPeople />} /></NavLink>
|
||||
<NavLink to='/'><MenuItem primaryText='Home' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgHome />} /></NavLink>
|
||||
<NavLink to={`/${this.props.uid}`}><MenuItem primaryText='Profile' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgAccountCircle />} /></NavLink>
|
||||
<NavLink to='/people'><MenuItem primaryText='People' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgPeople />} /></NavLink>
|
||||
<Divider />
|
||||
<NavLink to='/settings'><MenuItem primaryText="Settings" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgSettings />} /></NavLink>
|
||||
<NavLink to='#'><MenuItem primaryText="Send feedback" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgFeedback />} /></NavLink>
|
||||
<NavLink to='/settings'><MenuItem primaryText='Settings' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgSettings />} /></NavLink>
|
||||
<NavLink to='#'><MenuItem primaryText='Send feedback' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgFeedback />} /></NavLink>
|
||||
</Menu>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarMain>
|
||||
<Switch>
|
||||
<Route path="/people/:tab?" render={() => {
|
||||
<Route path='/people/:tab?' render={() => {
|
||||
return (
|
||||
this.props.authed
|
||||
? <People />
|
||||
: <Redirect to="/login" />
|
||||
: <Redirect to='/login' />
|
||||
)
|
||||
}} />
|
||||
<Route path="/tag/:tag" render={({match}) => {
|
||||
<Route path='/tag/:tag' render={({match}) => {
|
||||
|
||||
return (
|
||||
this.props.authed
|
||||
? <div className="blog"><Blog displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
|
||||
: <Redirect to="/login" />
|
||||
? <div className='blog'><StreamComponent displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
|
||||
: <Redirect to='/login' />
|
||||
)
|
||||
}} />
|
||||
<Route path="/:userId/posts/:postId/:tag?" component={PostPage} />
|
||||
<Route path="/:userId" component={Profile} />
|
||||
<Route path='/:userId/posts/:postId/:tag?' component={PostPage} />
|
||||
<Route path='/:userId' component={Profile} />
|
||||
|
||||
<Route path="/" render={() => {
|
||||
<Route path='/' render={() => {
|
||||
|
||||
return (
|
||||
this.props.authed
|
||||
? <div className="blog"><Blog homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
|
||||
: <Redirect to="/login" />
|
||||
? <div className='blog'><StreamComponent homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
|
||||
: <Redirect to='/login' />
|
||||
)
|
||||
}} />
|
||||
</Switch>
|
||||
@@ -167,10 +166,9 @@ export class Home extends Component {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
|
||||
return {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,15 +178,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
|
||||
const { uid } = state.authorize
|
||||
let mergedPosts = {}
|
||||
const circles = state.circle ? (state.circle.userCircles[uid] || {}) : {}
|
||||
const followingUsers = CircleAPI.getFollowingUsers(circles)
|
||||
const posts = state.post.userPosts ? state.post.userPosts[state.authorize.uid] : {}
|
||||
Object.keys(followingUsers).forEach((userId)=>{
|
||||
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
|
||||
_.merge(mergedPosts,newPosts)
|
||||
Object.keys(followingUsers).forEach((userId) => {
|
||||
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
|
||||
_.merge(mergedPosts,newPosts)
|
||||
})
|
||||
_.merge(mergedPosts,posts)
|
||||
return {
|
||||
@@ -199,4 +197,4 @@ const mapStateToProps = (state, ownProps) => {
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Home))
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HomeComponent as any))
|
||||
28
src/components/home/IHomeComponentProps.ts
Normal file
28
src/components/home/IHomeComponentProps.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Post } from 'core/domain/posts'
|
||||
|
||||
export interface IHomeComponentProps {
|
||||
|
||||
/**
|
||||
* Current user is authenticated {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeComponentProps
|
||||
*/
|
||||
authed?: boolean
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeComponentProps
|
||||
*/
|
||||
uid: string
|
||||
|
||||
/**
|
||||
* Merged all users posts to show in stream
|
||||
*
|
||||
* @type {{[postId: string]: Post}}
|
||||
* @memberof IHomeComponentProps
|
||||
*/
|
||||
mergedPosts?: {[postId: string]: Post}
|
||||
}
|
||||
27
src/components/home/IHomeComponentState.ts
Normal file
27
src/components/home/IHomeComponentState.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
export interface IHomeComponentState {
|
||||
|
||||
/**
|
||||
* Change sidebar status to {open(status:true)/close(status:false)}
|
||||
*
|
||||
* @type {(status: boolean, state: string)}
|
||||
* @memberof IHomeComponentState
|
||||
*/
|
||||
sidebarOpen: (status: boolean, source: string) => void
|
||||
|
||||
/**
|
||||
* Sidebar status
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeComponentState
|
||||
*/
|
||||
sidebarStatus: boolean
|
||||
|
||||
/**
|
||||
* Sidebar overlay status
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeComponentState
|
||||
*/
|
||||
sidebarOverlay: boolean
|
||||
}
|
||||
2
src/components/home/index.ts
Normal file
2
src/components/home/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import HomeComponent from './HomeComponent'
|
||||
export default HomeComponent
|
||||
@@ -14,24 +14,43 @@ import Paper from 'material-ui/Paper'
|
||||
import NotificationsIcon from 'material-ui/svg-icons/social/notifications'
|
||||
import EventListener, { withOptions } from 'react-event-listener'
|
||||
|
||||
|
||||
// - Import components
|
||||
import UserAvatar from 'UserAvatar'
|
||||
import Notify from 'Notify'
|
||||
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
import Notify from 'components/notify'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'globalActions'
|
||||
import * as authorizeActions from 'authorizeActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import { authorizeActions } from 'actions'
|
||||
import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps'
|
||||
import { IHomeHeaderComponentState } from './IHomeHeaderComponentState'
|
||||
|
||||
// - Create HomeHeader component class
|
||||
export class HomeHeader extends Component {
|
||||
export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps,IHomeHeaderComponentState> {
|
||||
|
||||
styles = {
|
||||
toolbarStyle: {
|
||||
backgroundColor: '',
|
||||
transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
|
||||
boxSizing: 'border-box',
|
||||
fontFamily: 'Roboto, sans-serif',
|
||||
position: 'fixed',
|
||||
zIndex: '1101',
|
||||
width: '100%',
|
||||
top: '0px',
|
||||
boxShadow: '0 1px 8px rgba(0,0,0,.3)'
|
||||
},
|
||||
avatarStyle: {
|
||||
margin: 5,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: IHomeHeaderComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
@@ -56,12 +75,10 @@ export class HomeHeader extends Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handle close notification menu
|
||||
*
|
||||
*
|
||||
* Handle close notification menu
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleCloseNotify = () => {
|
||||
@@ -70,78 +87,80 @@ export class HomeHeader extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// On click toggle sidebar
|
||||
onToggleSidebar = () => {
|
||||
if (this.props.sidebarStatus) {
|
||||
this.props.sidebar(false)
|
||||
this.props.sidebar!(false,'onToggle')
|
||||
|
||||
} else {
|
||||
this.props.sidebar(true)
|
||||
this.props.sidebar!(true,'onToggle')
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle notification touch
|
||||
*
|
||||
*
|
||||
* Handle notification touch
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleNotifyTouchTap = (event) => {
|
||||
handleNotifyTouchTap = (event: any) => {
|
||||
// This prevents ghost click.
|
||||
event.preventDefault()
|
||||
|
||||
this.setState({
|
||||
openNotifyMenu: true,
|
||||
anchorEl: event.currentTarget,
|
||||
anchorEl: event.currentTarget
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle touch on user avatar for popover
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleAvatarTouchTap = (event) => {
|
||||
handleAvatarTouchTap = (event: any) => {
|
||||
// This prevents ghost click.
|
||||
event.preventDefault()
|
||||
|
||||
this.setState({
|
||||
openAvatarMenu: true,
|
||||
anchorEl: event.currentTarget,
|
||||
anchorEl: event.currentTarget
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle logout user
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleLogout = () => {
|
||||
this.props.logout()
|
||||
this.props.logout!()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle close popover
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleRequestClose = () => {
|
||||
this.setState({
|
||||
openAvatarMenu: false,
|
||||
openAvatarMenu: false
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyUp = () => {
|
||||
// TODO: Handle key up on press ESC to close menu
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resize event for window to manipulate home header status
|
||||
* @param {event} evt is the event is passed by winodw resize event
|
||||
*/
|
||||
handleResize = (evt) => {
|
||||
handleResize = (event: any) => {
|
||||
|
||||
// Set initial state
|
||||
let width = window.innerWidth
|
||||
@@ -151,8 +170,7 @@ export class HomeHeader extends Component {
|
||||
showTitle: true
|
||||
})
|
||||
|
||||
}
|
||||
else if (width < 600 && this.state.showTitle) {
|
||||
} else if (width < 600 && this.state.showTitle) {
|
||||
|
||||
this.setState({
|
||||
showTitle: false
|
||||
@@ -160,54 +178,30 @@ export class HomeHeader extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
this.handleResize()
|
||||
componentDidMount () {
|
||||
this.handleResize(null)
|
||||
}
|
||||
|
||||
|
||||
// Render app DOM component
|
||||
render() {
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
let styles = {
|
||||
toolbarStyle: {
|
||||
backgroundColor: "",
|
||||
transition: "all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms",
|
||||
boxSizing: "border-box",
|
||||
fontFamily: "Roboto, sans-serif",
|
||||
position: "fixed",
|
||||
zIndex: "1101",
|
||||
width: "100%",
|
||||
top: "0px",
|
||||
boxShadow: '0 1px 8px rgba(0,0,0,.3)'
|
||||
},
|
||||
avatarStyle: {
|
||||
margin: 5,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
render () {
|
||||
|
||||
return (
|
||||
|
||||
<Toolbar style={styles.toolbarStyle} className="g__greenBox">
|
||||
<Toolbar style={this.styles.toolbarStyle as any} className='g__greenBox'>
|
||||
<EventListener
|
||||
target="window"
|
||||
target='window'
|
||||
onResize={this.handleResize}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
/>
|
||||
{/* Left side */}
|
||||
<ToolbarGroup firstChild={true}>
|
||||
|
||||
<IconButton iconStyle={{ color: "#fff" }} onClick={this.onToggleSidebar} >
|
||||
<SvgDehaze style={{ color: "#fff", marginLeft: "15px", cursor: "pointer" }} />
|
||||
<IconButton iconStyle={{ color: '#fff' }} onClick={this.onToggleSidebar} >
|
||||
<SvgDehaze style={{ color: '#fff', marginLeft: '15px', cursor: 'pointer' }} />
|
||||
</IconButton>
|
||||
{/* Header title */}
|
||||
<ToolbarTitle style={{ color: "#fff", marginLeft: "15px" }} text="Green" />
|
||||
{this.state.showTitle ? <div className="homeHeader__page">{this.props.title}</div> : ''}
|
||||
<ToolbarTitle style={{ color: '#fff', marginLeft: '15px' }} text='Green' />
|
||||
{this.state.showTitle ? <div className='homeHeader__page'>{this.props.title}</div> : ''}
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
|
||||
@@ -215,26 +209,25 @@ export class HomeHeader extends Component {
|
||||
|
||||
{/* Notification */}
|
||||
<ToolbarGroup lastChild={true}>
|
||||
<div className="homeHeader__right">
|
||||
{this.props.notifyCount > 0 ? (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}>
|
||||
<div className="homeHeader__notify">
|
||||
<div className='homeHeader__right'>
|
||||
{this.props.notifyCount! > 0 ? (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
|
||||
<div className='homeHeader__notify'>
|
||||
<div className='title'>{this.props.notifyCount}</div>
|
||||
</div>
|
||||
</IconButton>)
|
||||
|
||||
: (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}>
|
||||
: (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
|
||||
<NotificationsIcon color='rgba(255, 255, 255, 0.87)' />
|
||||
</IconButton>)}
|
||||
<Notify open={this.state.openNotifyMenu} anchorEl={this.state.anchorEl} onRequestClose={this.handleCloseNotify}/>
|
||||
|
||||
|
||||
{/* User avatar*/}
|
||||
<UserAvatar
|
||||
<UserAvatarComponent
|
||||
onTouchTap={this.handleAvatarTouchTap}
|
||||
fullName={this.props.fullName}
|
||||
fileName={this.props.avatar}
|
||||
fullName={this.props.fullName!}
|
||||
fileName={this.props.avatar!}
|
||||
size={32}
|
||||
style={styles.avatarStyle}
|
||||
style={this.styles.avatarStyle}
|
||||
/>
|
||||
<Popover
|
||||
open={this.state.openAvatarMenu}
|
||||
@@ -244,8 +237,8 @@ export class HomeHeader extends Component {
|
||||
onRequestClose={this.handleRequestClose}
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText="MY ACCOUNT" />
|
||||
<MenuItem primaryText="LOGOUT" style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
|
||||
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText='MY ACCOUNT' />
|
||||
<MenuItem primaryText='LOGOUT' style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
|
||||
|
||||
</Menu>
|
||||
</Popover>
|
||||
@@ -254,26 +247,24 @@ export class HomeHeader extends Component {
|
||||
|
||||
</Toolbar>
|
||||
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// - Map dispatch to props
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: Function, ownProps: IHomeHeaderComponentProps) => {
|
||||
return {
|
||||
logout: () => dispatch(authorizeActions.dbLogout())
|
||||
}
|
||||
}
|
||||
|
||||
// - Map state to props
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
let notifyCount = state.notify.userNotifies
|
||||
const mapStateToProps = (state: any, ownProps: IHomeHeaderComponentProps) => {
|
||||
|
||||
let notifyCount = state.notify.userNotifies
|
||||
? Object
|
||||
.keys(state.notify.userNotifies)
|
||||
.filter((key)=> !state.notify.userNotifies[key].isSeen).length
|
||||
.filter((key) => !state.notify.userNotifies[key].isSeen).length
|
||||
: 0
|
||||
return {
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
|
||||
@@ -284,4 +275,4 @@ const mapStateToProps = (state, ownProps) => {
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(HomeHeader)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(HomeHeaderComponent as any)
|
||||
63
src/components/homeHeader/IHomeHeaderComponentProps.ts
Normal file
63
src/components/homeHeader/IHomeHeaderComponentProps.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import StringAPI from 'api/StringAPI'
|
||||
export interface IHomeHeaderComponentProps {
|
||||
|
||||
/**
|
||||
* Sidebar is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
sidebarStatus?: boolean
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
logout?: () => void
|
||||
|
||||
/**
|
||||
* Handle on resize window event
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
handleResize?: (event: any) => void
|
||||
|
||||
/**
|
||||
* Number of notifications
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
notifyCount?: number
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
fullName?: string
|
||||
|
||||
/**
|
||||
* User's avatar URL address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
avatar?: string
|
||||
|
||||
/**
|
||||
* Top bar title
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Toggle sidebar
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
sidebar?: (status: boolean, source: string) => void
|
||||
}
|
||||
35
src/components/homeHeader/IHomeHeaderComponentState.ts
Normal file
35
src/components/homeHeader/IHomeHeaderComponentState.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
export interface IHomeHeaderComponentState {
|
||||
|
||||
/**
|
||||
* Popover menu on avatar is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
openAvatarMenu: boolean
|
||||
|
||||
/**
|
||||
* Show top bar title {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
showTitle: boolean
|
||||
|
||||
/**
|
||||
* Notification menu is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
openNotifyMenu: boolean
|
||||
|
||||
/**
|
||||
* This is the DOM element that will be used to set the position of the popover.
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
anchorEl?: HTMLElement
|
||||
}
|
||||
2
src/components/homeHeader/index.ts
Normal file
2
src/components/homeHeader/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import HomeHeaderComponent from './HomeHeaderComponent'
|
||||
export default HomeHeaderComponent
|
||||
48
src/components/imageGallery/IImageGalleryComponentProps.ts
Normal file
48
src/components/imageGallery/IImageGalleryComponentProps.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
|
||||
export interface IImageGalleryComponentProps {
|
||||
|
||||
/**
|
||||
* Select image from image gallery
|
||||
*
|
||||
* @type {(URL: string,fullPath: string)}
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
set?: (URL: string,fullPath: string) => void
|
||||
|
||||
/**
|
||||
* Delete an image
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
deleteImage?: (imageId: string) => void
|
||||
|
||||
/**
|
||||
* Save image in image gallery
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
saveImageGallery?: (URL: string,fullPath: string) => void
|
||||
|
||||
/**
|
||||
* Change progress state
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
progressChange?: (percentage: number, status: boolean) => void
|
||||
|
||||
/**
|
||||
* Close image gallery
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
close?: () => void
|
||||
|
||||
/**
|
||||
* List of image in image gallery
|
||||
*
|
||||
* @type {Image[]}
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
images?: Image[]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IImageGalleryComponentState {
|
||||
|
||||
}
|
||||
256
src/components/imageGallery/ImageGalleryComponent.tsx
Normal file
256
src/components/imageGallery/ImageGalleryComponent.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
// - Impoer react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { GridList, GridTile } from 'material-ui/GridList'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import Subheader from 'material-ui/Subheader'
|
||||
import StarBorder from 'material-ui/svg-icons/toggle/star-border'
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton'
|
||||
import SvgUpload from 'material-ui/svg-icons/file/cloud-upload'
|
||||
import SvgAddImage from 'material-ui/svg-icons/image/add-a-photo'
|
||||
import SvgDelete from 'material-ui/svg-icons/action/delete'
|
||||
import { grey200, grey600 } from 'material-ui/styles/colors'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import uuid from 'uuid'
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
// - Import app components
|
||||
import Img from 'components/img'
|
||||
|
||||
// - Import API
|
||||
import FileAPI from 'api/FileAPI'
|
||||
import { IImageGalleryComponentProps } from './IImageGalleryComponentProps'
|
||||
import { IImageGalleryComponentState } from './IImageGalleryComponentState'
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
|
||||
/**
|
||||
* Create ImageGallery component class
|
||||
*/
|
||||
export class ImageGalleryComponent extends Component<IImageGalleryComponentProps, IImageGalleryComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Callback function to ser image url on parent component
|
||||
*/
|
||||
open: PropTypes.func
|
||||
}
|
||||
|
||||
styles = {
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around'
|
||||
},
|
||||
gridList: {
|
||||
width: 500,
|
||||
height: 450,
|
||||
overflowY: 'auto'
|
||||
},
|
||||
uploadButton: {
|
||||
verticalAlign: 'middle'
|
||||
},
|
||||
uploadInput: {
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
opacity: 0
|
||||
},
|
||||
deleteImage: {
|
||||
marginLeft: '5px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
addImage: {
|
||||
marginRight: '5px',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IImageGalleryComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Binding function to `this`
|
||||
this.close = this.close.bind(this)
|
||||
this.onFileChange = this.onFileChange.bind(this)
|
||||
this.handleSetImage = this.handleSetImage.bind(this)
|
||||
this.handleDeleteImage = this.handleDeleteImage.bind(this)
|
||||
this.imageList = this.imageList.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle set image
|
||||
* @param {event} evt passed by on click event on add image
|
||||
* @param {string} name is the name of the image
|
||||
*/
|
||||
handleSetImage = (event: any, URL: string,fullPath: string) => {
|
||||
this.props.set!(URL,fullPath)
|
||||
this.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete image
|
||||
* @param {event} evt passed by on click event on delete image
|
||||
* @param {integer} id is the image identifier which selected to delete
|
||||
*/
|
||||
handleDeleteImage = (event: any, id: string) => {
|
||||
this.props.deleteImage!(id)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('onSendResizedImage', this.handleSendResizedImage)
|
||||
}
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('onSendResizedImage', this.handleSendResizedImage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle send image resize event that pass the resized image
|
||||
*
|
||||
*
|
||||
* @memberof ImageGallery
|
||||
*/
|
||||
handleSendResizedImage = (event: any) => {
|
||||
|
||||
const { resizedImage, fileName } = event.detail
|
||||
const {saveImageGallery, progressChange} = this.props
|
||||
|
||||
FileAPI.uploadImage(resizedImage, fileName, (percent: number, status: boolean) => {
|
||||
progressChange!(percent,status)
|
||||
}).then((result) => {
|
||||
|
||||
/* Add image to image gallery */
|
||||
saveImageGallery!(result.downloadURL,result.metadata.fullPath)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle on change file upload
|
||||
*/
|
||||
onFileChange = (event: any) => {
|
||||
|
||||
const extension = FileAPI.getExtension(event.target.files[0].name)
|
||||
let fileName = (`${uuid()}.${extension}`)
|
||||
let image = FileAPI.constraintImage(event.target.files[0], fileName)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide image gallery
|
||||
*/
|
||||
close = () => {
|
||||
this.props.close!()
|
||||
}
|
||||
|
||||
imageList = () => {
|
||||
|
||||
return this.props.images!.map((image: Image, index) => {
|
||||
|
||||
return (<GridTile
|
||||
key={image.id!}
|
||||
title={<SvgDelete hoverColor={grey200} color='white' style={this.styles.deleteImage as any} onClick={evt => this.handleDeleteImage(evt, image.id!)} />}
|
||||
subtitle={<span></span>}
|
||||
actionIcon={<SvgAddImage hoverColor={grey200} color='white' style={this.styles.addImage as any} onClick={evt => this.handleSetImage(evt, image.URL,image.fullPath)} />}
|
||||
>
|
||||
<div>
|
||||
<div style={{ overflowY: 'hidden', overflowX: 'auto' }}>
|
||||
<ul style={{ whiteSpace: 'nowrap', padding: '0 6px', margin: '8px 0 0 0', verticalAlign: 'bottom', flexShrink: 0, listStyleType: 'none' }}>
|
||||
<div style={{ display: 'block' }}>
|
||||
<div style={{ display: 'block', marginRight: '8px', transition: 'transform .25s' }}>
|
||||
<li style={{ width: '100%', margin: 0, verticalAlign: 'bottom', position: 'static', display: 'inline-block' }}>
|
||||
<Img fileName={image.URL} style={{ width: '100%', height: 'auto' }} />
|
||||
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</GridTile>)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When the post text changed
|
||||
* @param {event} evt is an event passed by change post text callback funciton
|
||||
* @param {string} data is the post content which user writes
|
||||
*/
|
||||
render () {
|
||||
|
||||
/**
|
||||
* Component styles
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
return (
|
||||
<div style={this.styles.root as any}>
|
||||
<GridList
|
||||
cellHeight={180}
|
||||
style={this.styles.gridList as any}
|
||||
>
|
||||
<GridTile >
|
||||
|
||||
<div style={{ display: 'flex', backgroundColor: 'rgba(222, 222, 222, 0.52)', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||
|
||||
<FlatButton
|
||||
label='Upload Photo'
|
||||
labelStyle={{ fontWeight: 100 }}
|
||||
labelPosition='before'
|
||||
style={this.styles.uploadButton}
|
||||
containerElement='label'
|
||||
>
|
||||
<input type='file' onChange={this.onFileChange} accept='image/*' style={this.styles.uploadInput as any} />
|
||||
</FlatButton>
|
||||
</div>
|
||||
</GridTile>
|
||||
{this.imageList()}
|
||||
</GridList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IImageGalleryComponentProps) => {
|
||||
return {
|
||||
saveImageGallery: (imageURL: string,imageFullPath: string) => dispatch(imageGalleryActions.dbSaveImage(imageURL,imageFullPath)),
|
||||
deleteImage: (id: string) => dispatch(imageGalleryActions.dbDeleteImage(id)),
|
||||
progressChange : (percent: number,status: boolean) => dispatch(globalActions.progressChange(percent, status))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
images: state.imageGallery.images,
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : ''
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImageGalleryComponent as any)
|
||||
2
src/components/imageGallery/index.ts
Normal file
2
src/components/imageGallery/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ImageGalleryComponent from './ImageGalleryComponent'
|
||||
export default ImageGalleryComponent
|
||||
19
src/components/img/IImgComponentProps.ts
Normal file
19
src/components/img/IImgComponentProps.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface IImgComponentProps {
|
||||
|
||||
/**
|
||||
* Image file name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgComponentProps
|
||||
*/
|
||||
fileName: string
|
||||
|
||||
/**
|
||||
* Image style sheet
|
||||
*
|
||||
* @type {{}}
|
||||
* @memberof IImgComponentProps
|
||||
*/
|
||||
style?: {}
|
||||
|
||||
}
|
||||
11
src/components/img/IImgComponentState.ts
Normal file
11
src/components/img/IImgComponentState.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export interface IImgComponentState {
|
||||
|
||||
/**
|
||||
* Image is loaded {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IImgComponentProps
|
||||
*/
|
||||
isImageLoaded?: boolean
|
||||
}
|
||||
@@ -4,40 +4,51 @@ import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import SvgImage from 'material-ui/svg-icons/image/image'
|
||||
|
||||
|
||||
// - Import app components
|
||||
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'imageGalleryActions'
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import { IImgComponentProps } from './IImgComponentProps'
|
||||
import { IImgComponentState } from './IImgComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class Img extends Component {
|
||||
export class ImgComponent extends Component<IImgComponentProps,IImgComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Use for getting url address from server
|
||||
*/
|
||||
fileName: PropTypes.string,
|
||||
/**
|
||||
* Avatar style
|
||||
*/
|
||||
style: PropTypes.object
|
||||
styles = {
|
||||
loding: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
position: 'relative',
|
||||
color: '#cacecd',
|
||||
fontWeight: 100
|
||||
},
|
||||
loadingContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
loadingImage: {
|
||||
fill: 'aliceblue',
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: IImgComponentProps) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
// Defaul state
|
||||
this.state = {
|
||||
isImageLoaded: false
|
||||
}
|
||||
@@ -49,7 +60,7 @@ export class Img extends Component {
|
||||
|
||||
/**
|
||||
* Will be called on loading image
|
||||
*
|
||||
*
|
||||
* @memberof Img
|
||||
*/
|
||||
handleLoadImage = () => {
|
||||
@@ -62,38 +73,16 @@ export class Img extends Component {
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
const styles = {
|
||||
loding: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
position: 'relative',
|
||||
color: '#cacecd',
|
||||
fontWeight: 100
|
||||
},
|
||||
loadingContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
loadingImage: {
|
||||
fill: 'aliceblue',
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
render () {
|
||||
|
||||
let { fileName, style } = this.props
|
||||
let { isImageLoaded } = this.state
|
||||
return (
|
||||
<div>
|
||||
<img onLoad={this.handleLoadImage} src={fileName || ''} style={isImageLoaded ? style : { display: 'none' }} />
|
||||
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}>
|
||||
<div style={styles.loadingContent}>
|
||||
<SvgImage style={styles.loadingImage} />
|
||||
<div style={Object.assign({},{ backgroundColor: 'white' }, isImageLoaded ? { display: 'none' } : this.styles.loding) }>
|
||||
<div style={this.styles.loadingContent as any}>
|
||||
<SvgImage style={this.styles.loadingImage} />
|
||||
<div>Image has not loaded</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,17 +91,15 @@ export class Img extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IImgComponentProps) => {
|
||||
return {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +109,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state: any, ownProps: IImgComponentProps) => {
|
||||
return {
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
@@ -130,4 +117,4 @@ const mapStateToProps = (state, ownProps) => {
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Img)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImgComponent as any)
|
||||
2
src/components/img/index.ts
Normal file
2
src/components/img/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ImgComponent from './ImgComponent'
|
||||
export default ImgComponent
|
||||
42
src/components/imgCover/IImgCoverComponentProps.ts
Normal file
42
src/components/imgCover/IImgCoverComponentProps.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export interface IImgCoverComponentProps {
|
||||
|
||||
/**
|
||||
* Image file name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
fileName: string
|
||||
|
||||
/**
|
||||
* Image style sheet
|
||||
*
|
||||
* @type {{}}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
style?: {}
|
||||
|
||||
/**
|
||||
* Image with
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
width?: string
|
||||
|
||||
/**
|
||||
* Image height
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
height?: string
|
||||
|
||||
/**
|
||||
* Image border radius
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
borderRadius?: string
|
||||
}
|
||||
12
src/components/imgCover/IImgCoverComponentState.ts
Normal file
12
src/components/imgCover/IImgCoverComponentState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
export interface IImgCoverComponentState {
|
||||
|
||||
/**
|
||||
* Image is loaded {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
isImageLoaded: boolean
|
||||
|
||||
}
|
||||
@@ -6,17 +6,17 @@ import SvgImage from 'material-ui/svg-icons/image/image'
|
||||
|
||||
// - Import app components
|
||||
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'imageGalleryActions'
|
||||
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import { IImgCoverComponentProps } from './IImgCoverComponentProps'
|
||||
import { IImgCoverComponentState } from './IImgCoverComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class ImgCover extends Component {
|
||||
export class ImgCoverComponent extends Component<IImgCoverComponentProps,IImgCoverComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
@@ -47,14 +47,42 @@ export class ImgCover extends Component {
|
||||
])
|
||||
}
|
||||
|
||||
styles = {
|
||||
cover: {
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center'
|
||||
},
|
||||
loding: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
position: 'relative',
|
||||
color: '#cacecd',
|
||||
fontWeight: 100
|
||||
},
|
||||
loadingContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
loadingImage: {
|
||||
fill: 'aliceblue',
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: IImgCoverComponentProps) {
|
||||
super(props)
|
||||
|
||||
//Defaul state
|
||||
// Defaul state
|
||||
this.state = {
|
||||
isImageLoaded: false
|
||||
}
|
||||
@@ -65,8 +93,8 @@ export class ImgCover extends Component {
|
||||
|
||||
/**
|
||||
* Will be called on loading image
|
||||
*
|
||||
* @memberof Img
|
||||
*
|
||||
* @memberof ImgCoverComponent
|
||||
*/
|
||||
handleLoadImage = () => {
|
||||
this.setState({
|
||||
@@ -78,54 +106,24 @@ export class ImgCover extends Component {
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
render () {
|
||||
|
||||
let { fileName, style } = this.props
|
||||
let { isImageLoaded } = this.state
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
const styles = {
|
||||
cover: {
|
||||
backgroundImage: 'url(' + (fileName || '') + ')',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
borderRadius: this.props.borderRadius
|
||||
|
||||
},
|
||||
loding: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
position: 'relative',
|
||||
color: '#cacecd',
|
||||
fontWeight: 100
|
||||
},
|
||||
loadingContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
loadingImage: {
|
||||
fill: 'aliceblue',
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div style={Object.assign({},styles.cover,style)}>
|
||||
<div style={Object.assign({},this.styles.cover,{
|
||||
backgroundImage: 'url(' + (fileName || '') + ')',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
borderRadius: this.props.borderRadius
|
||||
},style)}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}>
|
||||
<div style={styles.loadingContent}>
|
||||
<SvgImage style={styles.loadingImage} />
|
||||
<div style={Object.assign({},{ backgroundColor: 'blue' },isImageLoaded ? { display: 'none' } : this.styles.loding)}>
|
||||
<div style={this.styles.loadingContent as any}>
|
||||
<SvgImage style={this.styles.loadingImage} />
|
||||
<div>Image has not loaded</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,15 +133,13 @@ export class ImgCover extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IImgCoverComponentProps) => {
|
||||
return {
|
||||
}
|
||||
}
|
||||
@@ -154,7 +150,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => {
|
||||
return {
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
@@ -163,4 +159,4 @@ const mapStateToProps = (state, ownProps) => {
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImgCover)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImgCoverComponent as any)
|
||||
2
src/components/imgCover/index.ts
Normal file
2
src/components/imgCover/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ImgCoverComponent from './ImgCoverComponent'
|
||||
export default ImgCoverComponent
|
||||
16
src/components/login/ILoginComponentProps.ts
Normal file
16
src/components/login/ILoginComponentProps.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface ILoginComponentProps {
|
||||
|
||||
/**
|
||||
* Login a user
|
||||
*
|
||||
* @memberof ILoginComponentProps
|
||||
*/
|
||||
login?: (email: string , password: string) => any
|
||||
|
||||
/**
|
||||
* Redirect to signup page
|
||||
*
|
||||
* @memberof ILoginComponentProps
|
||||
*/
|
||||
signupPage?: () => any
|
||||
}
|
||||
43
src/components/login/ILoginComponentState.ts
Normal file
43
src/components/login/ILoginComponentState.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
export interface ILoginComponentState {
|
||||
|
||||
/**
|
||||
* Email input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
emailInput: string
|
||||
|
||||
/**
|
||||
* Email input error text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
emailInputError: string
|
||||
|
||||
/**
|
||||
* Password input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
passwordInput: string
|
||||
|
||||
/**
|
||||
* Password input error text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
passwordInputError: string
|
||||
|
||||
/**
|
||||
* Confirm input error text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
confirmInputError: string
|
||||
}
|
||||
@@ -9,17 +9,18 @@ import RaisedButton from 'material-ui/RaisedButton'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
|
||||
// - Import actions
|
||||
import * as authorizeActions from 'authorizeActions'
|
||||
|
||||
import * as authorizeActions from 'actions/authorizeActions'
|
||||
import { ILoginComponentProps } from './ILoginComponentProps'
|
||||
import { ILoginComponentState } from './ILoginComponentState'
|
||||
|
||||
// - Create Login component class
|
||||
export class Login extends Component {
|
||||
export class LoginComponent extends Component<ILoginComponentProps,ILoginComponentState> {
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor (props: ILoginComponentProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -27,8 +28,7 @@ export class Login extends Component {
|
||||
emailInputError: '',
|
||||
passwordInput: '',
|
||||
passwordInputError: '',
|
||||
|
||||
|
||||
confirmInputError: ''
|
||||
}
|
||||
// Binding function to `this`
|
||||
this.handleForm = this.handleForm.bind(this)
|
||||
@@ -39,15 +39,14 @@ export class Login extends Component {
|
||||
* Handle data on input change
|
||||
* @param {event} evt is an event of inputs of element on change
|
||||
*/
|
||||
handleInputChange = (evt) => {
|
||||
const target = evt.target
|
||||
handleInputChange = (event: any) => {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
[name]: value
|
||||
})
|
||||
|
||||
|
||||
switch (name) {
|
||||
case 'emailInput':
|
||||
this.setState({
|
||||
@@ -88,7 +87,7 @@ export class Login extends Component {
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
this.props.login(
|
||||
this.props.login!(
|
||||
this.state.emailInput,
|
||||
this.state.passwordInput
|
||||
)
|
||||
@@ -96,48 +95,46 @@ export class Login extends Component {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render() {
|
||||
render () {
|
||||
|
||||
const paperStyle = {
|
||||
minHeight: 370,
|
||||
width: 450,
|
||||
margin: 20,
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
margin: "auto"
|
||||
margin: 'auto'
|
||||
}
|
||||
return (
|
||||
<form>
|
||||
|
||||
<h1 style={{
|
||||
textAlign: "center",
|
||||
padding: "20px",
|
||||
fontSize: "30px",
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
fontSize: '30px',
|
||||
fontWeight: 500,
|
||||
lineHeight: "32px",
|
||||
margin: "auto",
|
||||
color: "rgba(138, 148, 138, 0.2)"
|
||||
lineHeight: '32px',
|
||||
margin: 'auto',
|
||||
color: 'rgba(138, 148, 138, 0.2)'
|
||||
}}>Green</h1>
|
||||
|
||||
<div className="animate-bottom">
|
||||
<div className='animate-bottom'>
|
||||
<Paper style={paperStyle} zDepth={1} rounded={false} >
|
||||
<div style={{ padding: "48px 40px 36px" }}>
|
||||
<div style={{ padding: '48px 40px 36px' }}>
|
||||
<div style={{
|
||||
paddingLeft: "40px",
|
||||
paddingRight: "40px"
|
||||
paddingLeft: '40px',
|
||||
paddingRight: '40px'
|
||||
}}>
|
||||
|
||||
<h2 style={{
|
||||
textAlign: "left",
|
||||
paddingTop: "16px",
|
||||
fontSize: "24px",
|
||||
textAlign: 'left',
|
||||
paddingTop: '16px',
|
||||
fontSize: '24px',
|
||||
fontWeight: 400,
|
||||
lineHeight: "32px",
|
||||
lineHeight: '32px',
|
||||
margin: 0
|
||||
}}>Sign in</h2>
|
||||
</div>
|
||||
@@ -145,29 +142,29 @@ export class Login extends Component {
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.emailInputError}
|
||||
name="emailInput"
|
||||
floatingLabelStyle={{ fontSize: "15px" }}
|
||||
floatingLabelText="Email"
|
||||
type="email"
|
||||
name='emailInput'
|
||||
floatingLabelStyle={{ fontSize: '15px' }}
|
||||
floatingLabelText='Email'
|
||||
type='email'
|
||||
tabIndex={1}
|
||||
/><br />
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.passwordInputError}
|
||||
name="passwordInput"
|
||||
floatingLabelStyle={{ fontSize: "15px" }}
|
||||
floatingLabelText="Password"
|
||||
type="password"
|
||||
name='passwordInput'
|
||||
floatingLabelStyle={{ fontSize: '15px' }}
|
||||
floatingLabelText='Password'
|
||||
type='password'
|
||||
tabIndex={2}
|
||||
/><br />
|
||||
<br />
|
||||
<br />
|
||||
<div className="login__button-box">
|
||||
<div className='login__button-box'>
|
||||
<div>
|
||||
<FlatButton label="Create an account" onClick={this.props.signupPage} tabIndex={4} />
|
||||
<FlatButton label='Create an account' onClick={this.props.signupPage} tabIndex={4} />
|
||||
</div>
|
||||
<div >
|
||||
<RaisedButton label="Login" primary={true} onClick={this.handleForm} tabIndex={3} />
|
||||
<RaisedButton label='Login' primary={true} onClick={this.handleForm} tabIndex={3} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -185,13 +182,13 @@ export class Login extends Component {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
|
||||
return {
|
||||
login: (username, password) => {
|
||||
dispatch(authorizeActions.dbLogin(username, password))
|
||||
login: (email: string, password: string) => {
|
||||
dispatch(authorizeActions.dbLogin(email, password))
|
||||
},
|
||||
signupPage: () => {
|
||||
dispatch(push("/signup"))
|
||||
dispatch(push('/signup'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,11 +199,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = (state: any, ownProps: ILoginComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login))
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginComponent as any))
|
||||
2
src/components/login/index.ts
Normal file
2
src/components/login/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import LoginComponent from './LoginComponent'
|
||||
export default LoginComponent
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IMasterProps {
|
||||
export interface IMasterComponentProps {
|
||||
/**
|
||||
* Close gloal message
|
||||
*
|
||||
@@ -1,24 +1,24 @@
|
||||
|
||||
export interface IMasterState {
|
||||
export interface IMasterComponentState {
|
||||
/**
|
||||
* Loding will be appeared if it's true
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @type {boolean}
|
||||
* @memberof IMasterState
|
||||
*/
|
||||
loading: Boolean,
|
||||
loading: boolean,
|
||||
/**
|
||||
* It's true if user is authorized
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @type {boolean}
|
||||
* @memberof IMasterState
|
||||
*/
|
||||
authed: Boolean
|
||||
authed: boolean
|
||||
/**
|
||||
* It's true if all default data loaded from database
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @type {boolean}
|
||||
* @memberof IMasterState
|
||||
*/
|
||||
dataLoaded: Boolean
|
||||
dataLoaded: boolean
|
||||
}
|
||||
@@ -3,22 +3,19 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom'
|
||||
import { firebaseAuth, firebaseRef } from 'app/firebaseClient'
|
||||
import { firebaseAuth, firebaseRef } from 'data/firebaseClient'
|
||||
import { push } from 'react-router-redux'
|
||||
import Snackbar from 'material-ui/Snackbar'
|
||||
import LinearProgress from 'material-ui/LinearProgress'
|
||||
|
||||
// - Import components
|
||||
import Home from 'components/Home'
|
||||
import Signup from 'components/Signup'
|
||||
import Login from 'components/Login'
|
||||
import Settings from 'components/Settings'
|
||||
import MasterLoading from 'components/MasterLoading'
|
||||
import { IMasterProps } from './IMasterProps'
|
||||
import { IMasterState } from './IMasterState'
|
||||
|
||||
// - Import API
|
||||
import { PrivateRoute, PublicRoute } from 'api/AuthRouterAPI'
|
||||
import Home from 'components/home'
|
||||
import Signup from 'components/signup'
|
||||
import Login from 'components/login'
|
||||
import Setting from 'components/setting'
|
||||
import MasterLoading from 'components/masterLoading'
|
||||
import { IMasterComponentProps } from './IMasterComponentProps'
|
||||
import { IMasterComponentState } from './IMasterComponentState'
|
||||
|
||||
// - Import actions
|
||||
import {
|
||||
@@ -36,11 +33,11 @@ import {
|
||||
/* ------------------------------------ */
|
||||
|
||||
// - Create Master component class
|
||||
export class Master extends Component<IMasterProps, IMasterState> {
|
||||
export class MasterComponent extends Component<IMasterComponentProps, IMasterComponentState> {
|
||||
|
||||
static isPrivate = true
|
||||
// Constructor
|
||||
constructor (props: IMasterProps) {
|
||||
constructor (props: IMasterComponentProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
loading: true,
|
||||
@@ -68,8 +65,9 @@ export class Master extends Component<IMasterProps, IMasterState> {
|
||||
}
|
||||
|
||||
componentDidCatch (error: any, info: any) {
|
||||
console.log('====================================')
|
||||
console.log('===========Catched by React componentDidCatch==============')
|
||||
console.log(error, info)
|
||||
alert({error, info})
|
||||
console.log('====================================')
|
||||
}
|
||||
|
||||
@@ -127,7 +125,7 @@ export class Master extends Component<IMasterProps, IMasterState> {
|
||||
{(!this.state.loading && (this.props.loaded || this.props.guest))
|
||||
? (<Switch>
|
||||
<Route path='/signup' component={Signup} />
|
||||
<Route path='/settings' component={Settings} />
|
||||
<Route path='/settings' component={Setting} />
|
||||
<Route path='/login' render={() => {
|
||||
console.log('this.props.authed: ', this.props.authed, 'this.props: ', this.props)
|
||||
return (
|
||||
@@ -154,7 +152,7 @@ export class Master extends Component<IMasterProps, IMasterState> {
|
||||
}
|
||||
|
||||
// - Map dispatch to props
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: any) => {
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IMasterComponentProps) => {
|
||||
|
||||
return {
|
||||
loadData: () => {
|
||||
@@ -175,6 +173,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: any) => {
|
||||
dispatch(voteActions.clearAllvotes())
|
||||
dispatch(notifyActions.clearAllNotifications())
|
||||
dispatch(circleActions.clearAllCircles())
|
||||
dispatch(globalActions.clearTemp())
|
||||
|
||||
},
|
||||
login: (user: any) => {
|
||||
@@ -216,4 +215,4 @@ const mapStateToProps = (state: any) => {
|
||||
|
||||
}
|
||||
// - Connect commponent to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Master as any))
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MasterComponent as any))
|
||||
2
src/components/master/index.ts
Normal file
2
src/components/master/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import MasterComponent from './MasterComponent'
|
||||
export default MasterComponent
|
||||
12
src/components/masterLoading/IMasterLoadingComponentProps.ts
Normal file
12
src/components/masterLoading/IMasterLoadingComponentProps.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface IMasterLoadingComponentProps {
|
||||
|
||||
/**
|
||||
* Loading is active {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IMasterLoadingComponentProps
|
||||
*/
|
||||
activeLoading: boolean
|
||||
|
||||
handleLoading: (status: boolean) => void
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IMasterLoadingComponentState {
|
||||
|
||||
}
|
||||
47
src/components/masterLoading/MasterLoadingComponent.tsx
Normal file
47
src/components/masterLoading/MasterLoadingComponent.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import CircularProgress from 'material-ui/CircularProgress'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import { IMasterLoadingComponentProps } from './IMasterLoadingComponentProps'
|
||||
import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Create MasterLoading component class
|
||||
export default class MasterLoadingComponent extends Component<IMasterLoadingComponentProps,IMasterLoadingComponentState> {
|
||||
|
||||
// Constructor
|
||||
constructor (props: IMasterLoadingComponentProps) {
|
||||
super(props)
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
// Render app DOM component
|
||||
render () {
|
||||
return (
|
||||
<Dialog
|
||||
modal={true}
|
||||
open={this.props.activeLoading}
|
||||
autoDetectWindowHeight={false}
|
||||
overlayStyle={{backgroundColor: 'white'}}
|
||||
contentClassName='mLoading__content'
|
||||
bodyStyle={{backgroundColor: ''}}
|
||||
bodyClassName='mLoading__body'
|
||||
>
|
||||
|
||||
<div>
|
||||
<div className='mLoading__context'>
|
||||
|
||||
<CircularProgress color='white' size={80} thickness={7} />
|
||||
<h1 style={{float: 'right', color: '#fff'}}>Green</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Dialog>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
2
src/components/masterLoading/index.ts
Normal file
2
src/components/masterLoading/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import MasterLoadingComponent from './MasterLoadingComponent'
|
||||
export default MasterLoadingComponent
|
||||
45
src/components/notify/INotifyComponentProps.ts
Normal file
45
src/components/notify/INotifyComponentProps.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Notification } from 'core/domain/notifications'
|
||||
|
||||
export interface INotifyComponentProps {
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*
|
||||
* @type {{[notificationId: string]: Notification}}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
notifications?: {[notificationId: string]: Notification}
|
||||
|
||||
/**
|
||||
* Users' profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
info?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* Close notification
|
||||
*
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
onRequestClose: () => void
|
||||
|
||||
/**
|
||||
* User notifications popover is opem {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Keep element
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
anchorEl: any
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user