This repository has been archived by the owner on Aug 22, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 116
Channel UI code refactoring #36
Closed
Closed
Changes from 8 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
5f09201
Create Spinner component
claus b65973f
Create ChannelControls component
claus e3700d9
Use Spinner component in ChannelsPanel
claus eecb80e
DRY common TransitionGroup props
claus 4929abf
Create NewMessageNotification component
claus ac4ac5b
Clean up render method
claus ec7377e
make file uploads/previews work
claus bba27c1
Make preview for text files work
claus f94ccb4
be consistent with use of parentheses in fat arrow function defs
claus 9868c31
Clarify what readUTF8String() does
claus 897447e
Move time formatting to constructor
claus b211c51
add mimeType to file meta data
claus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,16 +2,15 @@ | |
|
||
import _ from 'lodash'; | ||
import React from 'react'; | ||
import TransitionGroup from "react-addons-css-transition-group"; | ||
import Message from 'components/Message'; | ||
import SendMessage from 'components/SendMessage'; | ||
import ChannelControls from 'components/ChannelControls'; | ||
import NewMessageNotification from 'components/NewMessageNotification'; | ||
import Dropzone from 'react-dropzone'; | ||
import MessageStore from 'stores/MessageStore'; | ||
import ChannelStore from 'stores/ChannelStore'; | ||
import LoadingStateStore from 'stores/LoadingStateStore'; | ||
import UIActions from 'actions/UIActions'; | ||
import ChannelActions from 'actions/ChannelActions'; | ||
import Halogen from 'halogen'; | ||
import 'styles/Channel.scss'; | ||
import Logger from 'logplease'; | ||
const logger = Logger.create('Channel', { color: Logger.Colors.Cyan }); | ||
|
@@ -31,7 +30,6 @@ class Channel extends React.Component { | |
error: null, | ||
dragEnter: false, | ||
username: props.user ? props.user.username : '', | ||
displayNewMessagesIcon: false, | ||
unreadMessages: 0, | ||
appSettings: props.appSettings, | ||
theme: props.theme | ||
|
@@ -48,7 +46,7 @@ class Channel extends React.Component { | |
if(nextProps.channel !== this.state.channelName) { | ||
this.setState({ | ||
channelChanged: true, | ||
displayNewMessagesIcon: false, | ||
unreadMessages: 0, | ||
loading: true, | ||
reachedChannelStart: false, | ||
messages: [] | ||
|
@@ -158,7 +156,6 @@ class Channel extends React.Component { | |
&& this.state.messages.length > 0 && _.last(messages).payload.meta.ts > _.last(this.state.messages).payload.meta.ts | ||
&& this.node.scrollHeight > 0) { | ||
this.setState({ | ||
displayNewMessagesIcon: true, | ||
unreadMessages: this.state.unreadMessages + 1 | ||
}); | ||
} | ||
|
@@ -171,9 +168,9 @@ class Channel extends React.Component { | |
ChannelActions.sendMessage(this.state.channelName, text); | ||
} | ||
|
||
sendFile(filePath: string, buffer) { | ||
sendFile(filePath: string, buffer, mimeType = 'application/octet-binary') { | ||
if(filePath !== '' || buffer !== null) | ||
ChannelActions.addFile(this.state.channelName, filePath, buffer); | ||
ChannelActions.addFile(this.state.channelName, filePath, buffer, mimeType); | ||
} | ||
|
||
loadOlderMessages() { | ||
|
@@ -230,7 +227,7 @@ class Channel extends React.Component { | |
const reader = new FileReader(); | ||
reader.onload = (event) => { | ||
console.log(file, event); | ||
this.sendFile(file.name, event.target.result) | ||
this.sendFile(file.name, event.target.result, file.type); | ||
}; | ||
reader.readAsArrayBuffer(file); | ||
// console.error("File upload not yet implemented in browser. Try the electron app."); | ||
|
@@ -261,7 +258,9 @@ class Channel extends React.Component { | |
// If we scrolled to the bottom, hide the "new messages" label | ||
this.node = this.refs.MessagesView; | ||
if(this.node.scrollHeight - this.node.scrollTop - 10 <= this.node.clientHeight) { | ||
this.setState({ displayNewMessagesIcon: false, unreadMessages: 0 }); | ||
this.setState({ | ||
unreadMessages: 0 | ||
}); | ||
} | ||
} | ||
|
||
|
@@ -270,89 +269,70 @@ class Channel extends React.Component { | |
this.node.scrollTop = this.node.scrollHeight + this.node.clientHeight; | ||
} | ||
|
||
render() { | ||
const theme = this.state.theme; | ||
const channelMode = (<div className={"statusMessage"} style={theme}>{this.state.channelMode}</div>); | ||
|
||
const controlsBar = ( | ||
<TransitionGroup | ||
component="div" | ||
transitionName="controlsAnimation" | ||
transitionAppear={true} | ||
transitionAppearTimeout={1000} | ||
transitionEnterTimeout={0} | ||
transitionLeaveTimeout={0} | ||
> | ||
<div className="Controls" key="controls"> | ||
<SendMessage onSendMessage={this.sendMessage.bind(this)} key="SendMessage" theme={theme} useEmojis={this.state.appSettings.useEmojis}/> | ||
<Dropzone className="dropzone2" onDrop={this.onDrop.bind(this)} key="dropzone2"> | ||
<div className="icon flaticon-tool490" style={theme} key="icon"/> | ||
</Dropzone> | ||
</div> | ||
</TransitionGroup> | ||
); | ||
|
||
const messages = this.state.messages.map((e) => { | ||
return <Message | ||
message={e.payload} | ||
key={e.hash} | ||
onDragEnter={this.onDragEnter.bind(this)} | ||
highlightWords={this.state.username} | ||
colorifyUsername={this.state.appSettings.colorifyUsernames} | ||
useEmojis={this.state.appSettings.useEmojis} | ||
style={{ | ||
fontFamily: this.state.appSettings.useMonospaceFont ? this.state.appSettings.monospaceFont : this.state.appSettings.font, | ||
fontSize: this.state.appSettings.useMonospaceFont ? '0.9em' : '1.0em', | ||
fontWeight: this.state.appSettings.useMonospaceFont ? '100' : '300', | ||
padding: this.state.appSettings.spacing, | ||
}} | ||
/>; | ||
}); | ||
|
||
// let channelStateText = this.state.loading && this.state.loadingText ? this.state.loadingText : `Loading messages...`; | ||
let channelStateText = this.state.loadingText ? this.state.loadingText : `???`; | ||
if(this.state.reachedChannelStart && !this.state.loading) | ||
channelStateText = `Beginning of #${this.state.channelName}`; | ||
|
||
messages.unshift(<div className="firstMessage" key="firstMessage" onClick={this.loadOlderMessages.bind(this)}>{channelStateText}</div>); | ||
|
||
const fileDrop = this.state.dragEnter ? ( | ||
<Dropzone | ||
className="dropzone" | ||
activeClassName="dropzoneActive" | ||
disableClick={true} | ||
onDrop={this.onDrop.bind(this)} | ||
renderMessages() { | ||
const { messages, username, channelName, loading, loadingText, reachedChannelStart, appSettings } = this.state; | ||
const { colorifyUsernames, useEmojis, useMonospaceFont, font, monospaceFont, spacing } = appSettings; | ||
const elements = messages.map(message => ( | ||
<Message | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, let's use parenthesis around function parameters to make it explicit that it's a function: const elements = messages.map((message) => ...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in f94ccb4 |
||
message={message.payload} | ||
key={message.hash} | ||
onDragEnter={this.onDragEnter.bind(this)} | ||
onDragLeave={this.onDragLeave.bind(this)} | ||
style={theme} | ||
key="dropzone" | ||
> | ||
<div ref="dropLabel" style={theme}>Add files to #{this.state.channelName}</div> | ||
</Dropzone> | ||
) : ""; | ||
|
||
const showNewMessageNotification = this.state.displayNewMessagesIcon ? ( | ||
<div className="newMessagesBar" onClick={this.onScrollToBottom.bind(this)}> | ||
There are <span className="newMessagesNumber">{this.state.unreadMessages}</span> new messages | ||
highlightWords={username} | ||
colorifyUsername={colorifyUsernames} | ||
useEmojis={useEmojis} | ||
style={{ | ||
fontFamily: useMonospaceFont ? monospaceFont : font, | ||
fontSize: useMonospaceFont ? '0.9em' : '1.0em', | ||
fontWeight: useMonospaceFont ? '100' : '300', | ||
padding: spacing, | ||
}} | ||
/> | ||
)); | ||
elements.unshift( | ||
<div className="firstMessage" key="firstMessage" onClick={this.loadOlderMessages.bind(this)}> | ||
{reachedChannelStart && !loading ? `Beginning of #${channelName}` : loadingText || '???'} | ||
</div> | ||
) : (<span></span>); | ||
); | ||
return elements; | ||
} | ||
|
||
const loadingIcon = this.state.loading ? ( | ||
<div className="loadingBar"> | ||
<Halogen.MoonLoader className="loadingIcon" color="rgba(255, 255, 255, 0.7)" size="16px"/> | ||
</div> | ||
) : ""; | ||
renderFileDrop() { | ||
const { theme, dragEnter, channelName } = this.state; | ||
if (dragEnter) { | ||
return ( | ||
<Dropzone | ||
className="dropzone" | ||
activeClassName="dropzoneActive" | ||
disableClick={true} | ||
onDrop={this.onDrop.bind(this)} | ||
onDragEnter={this.onDragEnter.bind(this)} | ||
onDragLeave={this.onDragLeave.bind(this)} | ||
style={theme} > | ||
<div ref="dropLabel" style={theme}>Add files to #{channelName}</div> | ||
</Dropzone> | ||
); | ||
} | ||
return null; | ||
} | ||
|
||
render() { | ||
const { unreadMessages, loading, channelMode, appSettings, theme } = this.state; | ||
return ( | ||
<div className="Channel flipped" onDragEnter={this.onDragEnter.bind(this)}> | ||
<div className="Messages" ref="MessagesView" onScroll={this.onScroll.bind(this)} > | ||
{messages} | ||
<div className="Messages" ref="MessagesView" onScroll={this.onScroll.bind(this)}> | ||
{this.renderMessages()} | ||
</div> | ||
{showNewMessageNotification} | ||
{controlsBar} | ||
{fileDrop} | ||
{loadingIcon} | ||
{channelMode} | ||
<NewMessageNotification | ||
onClick={this.onScrollToBottom.bind(this)} | ||
unreadMessages={unreadMessages} /> | ||
<ChannelControls | ||
onSendMessage={this.sendMessage.bind(this)} | ||
onSendFiles={this.onDrop.bind(this)} | ||
isLoading={loading} | ||
channelMode={channelMode} | ||
appSettings={appSettings} | ||
theme={theme} /> | ||
{this.renderFileDrop()} | ||
</div> | ||
); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
'use strict'; | ||
|
||
import React, { PropTypes } from 'react'; | ||
import TransitionGroup from "react-addons-css-transition-group"; | ||
import Dropzone from 'react-dropzone'; | ||
import Halogen from 'halogen'; | ||
import Spinner from 'components/Spinner'; | ||
import SendMessage from 'components/SendMessage'; | ||
|
||
class ChannelControls extends React.Component { | ||
|
||
static propTypes = { | ||
onSendMessage: PropTypes.func, | ||
onSendFiles: PropTypes.func, | ||
isLoading: PropTypes.bool, | ||
channelMode: PropTypes.string, | ||
appSettings: PropTypes.object, | ||
theme: PropTypes.object, | ||
}; | ||
|
||
render() { | ||
const { onSendMessage, onSendFiles, isLoading, channelMode, appSettings, theme } = this.props; | ||
return ( | ||
<TransitionGroup | ||
component="div" | ||
transitionName="controlsAnimation" | ||
transitionAppear={true} | ||
transitionAppearTimeout={1000} | ||
transitionEnterTimeout={0} | ||
transitionLeaveTimeout={0} | ||
> | ||
<div className="Controls" key="controls"> | ||
<Spinner isLoading={isLoading} color="rgba(255, 255, 255, 0.7)" size="16px" /> | ||
<SendMessage onSendMessage={onSendMessage} theme={theme} useEmojis={appSettings.useEmojis} /> | ||
<Dropzone className="dropzone2" onDrop={onSendFiles}> | ||
<div className="icon flaticon-tool490" style={theme} /> | ||
</Dropzone> | ||
<div className="statusMessage" style={theme}>{channelMode}</div> | ||
</div> | ||
</TransitionGroup> | ||
); | ||
} | ||
|
||
} | ||
|
||
export default ChannelControls; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice to have an optional mime type stored in file post metadata (
ipfs-post
would need to support it). Maybe even allow to pass in further metadata as a generic object?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add the mimeType to
ipfs-post
which is what defines the data structure for each message: https://github.com/haadcode/ipfs-post/blob/master/src/FilePost.js and https://github.com/haadcode/ipfs-post/blob/master/src/Post.js.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to add that or should i? I'm not sure if we should have an explicit
mimeType
property or just a more generic object that we can use for more meta data like encoding and whatever else might be useful in the future, i.e.{ name, hash, size, meta = { mimeType, encoding } }
vs.{ name, hash, size, mimeType }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely prefer the proposed
meta
field. Added it to ipfs-post (orbitdb-archive/ipfs-post@1224898).You can use it by adding
meta
field to youdata
object insendFile()