mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-12-26 16:09:35 -05:00
webui: added chat
This commit is contained in:
parent
1939ea9f54
commit
a9ef48d133
@ -227,7 +227,8 @@ public:
|
|||||||
mIdentityHandler(ifaces.mIdentity),
|
mIdentityHandler(ifaces.mIdentity),
|
||||||
mServiceControlHandler(rsServiceControl), // TODO: don't use global variable here
|
mServiceControlHandler(rsServiceControl), // TODO: don't use global variable here
|
||||||
mFileSearchHandler(sts, ifaces.mNotify, ifaces.mTurtle, ifaces.mFiles),
|
mFileSearchHandler(sts, ifaces.mNotify, ifaces.mTurtle, ifaces.mFiles),
|
||||||
mTransfersHandler(sts, ifaces.mFiles)
|
mTransfersHandler(sts, ifaces.mFiles),
|
||||||
|
mChatHandler(sts, ifaces.mNotify, ifaces.mMsgs, ifaces.mPeers, ifaces.mIdentity, &mPeersHandler)
|
||||||
{
|
{
|
||||||
// the dynamic cast is to not confuse the addResourceHandler template like this:
|
// the dynamic cast is to not confuse the addResourceHandler template like this:
|
||||||
// addResourceHandler(derived class, parent class)
|
// addResourceHandler(derived class, parent class)
|
||||||
@ -243,6 +244,8 @@ public:
|
|||||||
&FileSearchHandler::handleRequest);
|
&FileSearchHandler::handleRequest);
|
||||||
router.addResourceHandler("transfers", dynamic_cast<ResourceRouter*>(&mTransfersHandler),
|
router.addResourceHandler("transfers", dynamic_cast<ResourceRouter*>(&mTransfersHandler),
|
||||||
&TransfersHandler::handleRequest);
|
&TransfersHandler::handleRequest);
|
||||||
|
router.addResourceHandler("chat", dynamic_cast<ResourceRouter*>(&mChatHandler),
|
||||||
|
&ChatHandler::handleRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
PeersHandler mPeersHandler;
|
PeersHandler mPeersHandler;
|
||||||
@ -250,6 +253,7 @@ public:
|
|||||||
ServiceControlHandler mServiceControlHandler;
|
ServiceControlHandler mServiceControlHandler;
|
||||||
FileSearchHandler mFileSearchHandler;
|
FileSearchHandler mFileSearchHandler;
|
||||||
TransfersHandler mTransfersHandler;
|
TransfersHandler mTransfersHandler;
|
||||||
|
ChatHandler mChatHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiServer::ApiServer():
|
ApiServer::ApiServer():
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "TransfersHandler.h"
|
#include "TransfersHandler.h"
|
||||||
#include "LivereloadHandler.h"
|
#include "LivereloadHandler.h"
|
||||||
#include "TmpBlobStore.h"
|
#include "TmpBlobStore.h"
|
||||||
|
#include "ChatHandler.h"
|
||||||
|
|
||||||
namespace resource_api{
|
namespace resource_api{
|
||||||
|
|
||||||
|
@ -240,7 +240,10 @@ public:
|
|||||||
std::ostream& mDebug;
|
std::ostream& mDebug;
|
||||||
|
|
||||||
inline void setOk(){mReturnCode = OK;}
|
inline void setOk(){mReturnCode = OK;}
|
||||||
inline void setWarning(){ mReturnCode = WARNING;}
|
inline void setWarning(std::string msg = ""){
|
||||||
|
mReturnCode = WARNING;
|
||||||
|
if(msg != "")
|
||||||
|
mDebug << msg << std::endl;}
|
||||||
inline void setFail(std::string msg = ""){
|
inline void setFail(std::string msg = ""){
|
||||||
mReturnCode = FAIL;
|
mReturnCode = FAIL;
|
||||||
if(msg != "")
|
if(msg != "")
|
||||||
|
@ -1 +1,654 @@
|
|||||||
#include "ChatHandler.h"
|
#include "ChatHandler.h"
|
||||||
|
#include "Pagination.h"
|
||||||
|
#include "Operators.h"
|
||||||
|
#include "GxsResponseTask.h"
|
||||||
|
|
||||||
|
#include <retroshare/rspeers.h>
|
||||||
|
#include <retroshare/rsidentity.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace resource_api
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string id(const ChatHandler::Msg& m)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << m.id;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamBase& operator << (StreamBase& left, ChatHandler::Msg& m)
|
||||||
|
{
|
||||||
|
left << makeKeyValueReference("incoming", m.incoming)
|
||||||
|
<< makeKeyValueReference("was_send", m.was_send)
|
||||||
|
<< makeKeyValueReference("author_id", m.author_id)
|
||||||
|
<< makeKeyValueReference("author_name", m.author_name)
|
||||||
|
<< makeKeyValueReference("msg", m.msg)
|
||||||
|
<< makeKeyValueReference("recv_time", m.recv_time)
|
||||||
|
<< makeKeyValueReference("send_time", m.send_time)
|
||||||
|
<< makeKeyValueReference("id", m.id);
|
||||||
|
StreamBase& ls = left.getStreamToMember("links");
|
||||||
|
ls.getStreamToMember();
|
||||||
|
for(std::vector<ChatHandler::Triple>::iterator vit = m.links.begin(); vit != m.links.end(); ++vit)
|
||||||
|
{
|
||||||
|
ls.getStreamToMember() << makeKeyValueReference("first", vit->first)
|
||||||
|
<< makeKeyValueReference("second", vit->second)
|
||||||
|
<< makeKeyValueReference("third", vit->third);
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compare_lobby_id(const ChatHandler::Lobby& l1, const ChatHandler::Lobby& l2)
|
||||||
|
{
|
||||||
|
return l1.id < l2.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamBase& operator <<(StreamBase& left, KeyValueReference<ChatLobbyId> kv)
|
||||||
|
{
|
||||||
|
if(left.serialise())
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << kv.value;
|
||||||
|
left << makeKeyValue(kv.key, ss.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string val;
|
||||||
|
left << makeKeyValueReference(kv.key, val);
|
||||||
|
std::stringstream ss(val);
|
||||||
|
ss >> kv.value;
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamBase& operator << (StreamBase& left, ChatHandler::Lobby& l)
|
||||||
|
{
|
||||||
|
left << makeKeyValueReference("id", l.id)
|
||||||
|
<< makeKeyValue("chat_id", ChatId(l.id).toStdString())
|
||||||
|
<< makeKeyValueReference("name",l.name)
|
||||||
|
<< makeKeyValueReference("topic", l.topic)
|
||||||
|
<< makeKeyValueReference("subscribed", l.subscribed)
|
||||||
|
<< makeKeyValueReference("auto_subscribe", l.auto_subscribe)
|
||||||
|
<< makeKeyValueReference("is_private", l.is_private)
|
||||||
|
<< makeKeyValueReference("gxs_id", l.gxs_id);
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamBase& operator << (StreamBase& left, ChatHandler::ChatInfo& info)
|
||||||
|
{
|
||||||
|
left << makeKeyValueReference("remote_author_id", info.remote_author_id)
|
||||||
|
<< makeKeyValueReference("remote_author_name", info.remote_author_name)
|
||||||
|
<< makeKeyValueReference("is_broadcast", info.is_broadcast)
|
||||||
|
<< makeKeyValueReference("is_gxs_id", info.is_gxs_id)
|
||||||
|
<< makeKeyValueReference("is_lobby", info.is_lobby)
|
||||||
|
<< makeKeyValueReference("is_peer", info.is_peer);
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatHandler::ChatHandler(StateTokenServer *sts, RsNotify *notify, RsMsgs *msgs, RsPeers* peers, RsIdentity* identity, UnreadMsgNotify* unread):
|
||||||
|
mStateTokenServer(sts), mNotify(notify), mRsMsgs(msgs), mRsPeers(peers), mRsIdentity(identity), mUnreadMsgNotify(unread), mMtx("ChatHandler::mMtx")
|
||||||
|
{
|
||||||
|
mNotify->registerNotifyClient(this);
|
||||||
|
mStateTokenServer->registerTickClient(this);
|
||||||
|
|
||||||
|
mMsgStateToken = mStateTokenServer->getNewToken();
|
||||||
|
mLobbiesStateToken = mStateTokenServer->getNewToken();
|
||||||
|
mUnreadMsgsStateToken = mStateTokenServer->getNewToken();
|
||||||
|
|
||||||
|
addResourceHandler("*", this, &ChatHandler::handleWildcard);
|
||||||
|
addResourceHandler("lobbies", this, &ChatHandler::handleLobbies);
|
||||||
|
addResourceHandler("subscribe_lobby", this, &ChatHandler::handleSubscribeLobby);
|
||||||
|
addResourceHandler("unsubscribe_lobby", this, &ChatHandler::handleUnsubscribeLobby);
|
||||||
|
addResourceHandler("messages", this, &ChatHandler::handleMessages);
|
||||||
|
addResourceHandler("send_message", this, &ChatHandler::handleSendMessage);
|
||||||
|
addResourceHandler("mark_chat_as_read", this, &ChatHandler::handleMarkChatAsRead);
|
||||||
|
addResourceHandler("info", this, &ChatHandler::handleInfo);
|
||||||
|
addResourceHandler("typing_label", this, &ChatHandler::handleTypingLabel);
|
||||||
|
addResourceHandler("send_status", this, &ChatHandler::handleSendStatus);
|
||||||
|
addResourceHandler("unread_msgs", this, &ChatHandler::handleUnreadMsgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatHandler::~ChatHandler()
|
||||||
|
{
|
||||||
|
mNotify->unregisterNotifyClient(this);
|
||||||
|
mStateTokenServer->unregisterTickClient(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::notifyChatMessage(const ChatMessage &msg)
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
mRawMsgs.push_back(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// to be removed
|
||||||
|
/*
|
||||||
|
ChatHandler::Lobby ChatHandler::getLobbyInfo(ChatLobbyId id)
|
||||||
|
{
|
||||||
|
tick();
|
||||||
|
|
||||||
|
RS_STACK_MUTEX(mMtx); // ********* LOCKED **********
|
||||||
|
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
|
||||||
|
if(vit->id == id)
|
||||||
|
return *vit;
|
||||||
|
std::cerr << "ChatHandler::getLobbyInfo Error: Lobby not found" << std::endl;
|
||||||
|
return Lobby();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ChatHandler::tick()
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
|
||||||
|
// first fetch lobbies
|
||||||
|
std::vector<Lobby> lobbies;
|
||||||
|
std::list<ChatLobbyId> subscribed_ids;
|
||||||
|
mRsMsgs->getChatLobbyList(subscribed_ids);
|
||||||
|
for(std::list<ChatLobbyId>::iterator lit = subscribed_ids.begin(); lit != subscribed_ids.end(); ++lit)
|
||||||
|
{
|
||||||
|
ChatLobbyInfo info;
|
||||||
|
if(mRsMsgs->getChatLobbyInfo(*lit,info))
|
||||||
|
{
|
||||||
|
Lobby l;
|
||||||
|
l.id = *lit;
|
||||||
|
l.name = info.lobby_name;
|
||||||
|
l.topic = info.lobby_topic;
|
||||||
|
l.subscribed = true;
|
||||||
|
l.auto_subscribe = info.lobby_flags & RS_CHAT_LOBBY_FLAGS_AUTO_SUBSCRIBE;
|
||||||
|
l.is_private = !(info.lobby_flags & RS_CHAT_LOBBY_FLAGS_PUBLIC);
|
||||||
|
l.gxs_id = info.gxs_id;
|
||||||
|
lobbies.push_back(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VisibleChatLobbyRecord> unsubscribed_lobbies;
|
||||||
|
mRsMsgs->getListOfNearbyChatLobbies(unsubscribed_lobbies);
|
||||||
|
for(std::vector<VisibleChatLobbyRecord>::iterator vit = unsubscribed_lobbies.begin(); vit != unsubscribed_lobbies.end(); ++vit)
|
||||||
|
{
|
||||||
|
const VisibleChatLobbyRecord& info = *vit;
|
||||||
|
if(std::find(subscribed_ids.begin(), subscribed_ids.end(), info.lobby_id) == subscribed_ids.end())
|
||||||
|
{
|
||||||
|
Lobby l;
|
||||||
|
l.id = info.lobby_id;
|
||||||
|
l.name = info.lobby_name;
|
||||||
|
l.topic = info.lobby_topic;
|
||||||
|
l.subscribed = false;
|
||||||
|
l.auto_subscribe = info.lobby_flags & RS_CHAT_LOBBY_FLAGS_AUTO_SUBSCRIBE;
|
||||||
|
l.is_private = !(info.lobby_flags & RS_CHAT_LOBBY_FLAGS_PUBLIC);
|
||||||
|
l.gxs_id = RsGxsId();
|
||||||
|
lobbies.push_back(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process new messages
|
||||||
|
bool changed = false;
|
||||||
|
bool lobby_unread_count_changed = false;
|
||||||
|
std::vector<std::list<ChatMessage>::iterator> done;
|
||||||
|
std::vector<RsPeerId> peers_changed;
|
||||||
|
|
||||||
|
bool gxs_id_failed = false; // to prevent asking for multiple failing gxs ids in one tick, to not flush the cache
|
||||||
|
|
||||||
|
for(std::list<ChatMessage>::iterator lit = mRawMsgs.begin(); lit != mRawMsgs.end(); ++lit)
|
||||||
|
{
|
||||||
|
ChatMessage& msg = *lit;
|
||||||
|
std::string author_id;
|
||||||
|
std::string author_name;
|
||||||
|
if(msg.chat_id.isBroadcast())
|
||||||
|
{
|
||||||
|
author_id = msg.broadcast_peer_id.toStdString();
|
||||||
|
author_name = mRsPeers->getPeerName(msg.broadcast_peer_id);
|
||||||
|
}
|
||||||
|
else if(msg.chat_id.isGxsId())
|
||||||
|
{
|
||||||
|
author_id = msg.chat_id.toGxsId().toStdString();
|
||||||
|
RsIdentityDetails details;
|
||||||
|
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.chat_id.toGxsId(), details))
|
||||||
|
{
|
||||||
|
author_name = details.mNickname;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gxs_id_failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(msg.chat_id.isLobbyId())
|
||||||
|
{
|
||||||
|
author_id = msg.lobby_peer_gxs_id.toStdString();
|
||||||
|
RsIdentityDetails details;
|
||||||
|
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.lobby_peer_gxs_id, details))
|
||||||
|
{
|
||||||
|
author_name = details.mNickname;
|
||||||
|
lobby_unread_count_changed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gxs_id_failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(msg.chat_id.isPeerId())
|
||||||
|
{
|
||||||
|
RsPeerId id;
|
||||||
|
if(msg.incoming)
|
||||||
|
id = msg.chat_id.toPeerId();
|
||||||
|
else
|
||||||
|
id = mRsPeers->getOwnId();
|
||||||
|
author_id = id.toStdString();
|
||||||
|
author_name = mRsPeers->getPeerName(id);
|
||||||
|
if(std::find(peers_changed.begin(), peers_changed.end(), msg.chat_id.toPeerId()) == peers_changed.end())
|
||||||
|
peers_changed.push_back(msg.chat_id.toPeerId());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Error in ChatHandler::tick(): unhandled chat_id=" << msg.chat_id.toStdString() << std::endl;
|
||||||
|
// remove from queue, so msgs with wrong ids to not pile up
|
||||||
|
done.push_back(lit);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mChatInfo.find(msg.chat_id) == mChatInfo.end())
|
||||||
|
{
|
||||||
|
ChatInfo info;
|
||||||
|
info.is_broadcast = msg.chat_id.isBroadcast();
|
||||||
|
info.is_gxs_id = msg.chat_id.isGxsId();
|
||||||
|
info.is_lobby = msg.chat_id.isLobbyId();
|
||||||
|
info.is_peer = msg.chat_id.isPeerId();
|
||||||
|
if(msg.chat_id.isLobbyId())
|
||||||
|
{
|
||||||
|
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
|
||||||
|
{
|
||||||
|
if(vit->id == msg.chat_id.toLobbyId())
|
||||||
|
info.remote_author_name = vit->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(msg.chat_id.isGxsId())
|
||||||
|
{
|
||||||
|
RsIdentityDetails details;
|
||||||
|
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.chat_id.toGxsId(), details))
|
||||||
|
{
|
||||||
|
info.remote_author_id = msg.chat_id.toGxsId().toStdString();
|
||||||
|
info.remote_author_name = details.mNickname;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gxs_id_failed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(msg.chat_id.isPeerId())
|
||||||
|
{
|
||||||
|
info.remote_author_id = msg.chat_id.toPeerId().toStdString();
|
||||||
|
info.remote_author_name = mRsPeers->getPeerName(msg.chat_id.toPeerId());
|
||||||
|
}
|
||||||
|
mChatInfo[msg.chat_id] = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
Msg m;
|
||||||
|
m.read = !msg.incoming;
|
||||||
|
m.incoming = msg.incoming;
|
||||||
|
m.was_send = msg.online;
|
||||||
|
m.author_id = author_id;
|
||||||
|
m.author_name = author_name;
|
||||||
|
|
||||||
|
// remove html tags from chat message
|
||||||
|
// extract links form href
|
||||||
|
const std::string& in = msg.msg;
|
||||||
|
std::string out;
|
||||||
|
bool ignore = false;
|
||||||
|
bool keep_link = false;
|
||||||
|
std::string last_six_chars;
|
||||||
|
Triple current_link;
|
||||||
|
std::vector<Triple> links;
|
||||||
|
for(unsigned int i = 0; i < in.size(); ++i)
|
||||||
|
{
|
||||||
|
if(keep_link && in[i] == '"')
|
||||||
|
{
|
||||||
|
keep_link = false;
|
||||||
|
current_link.second = out.size();
|
||||||
|
}
|
||||||
|
if(last_six_chars == "href=\"")
|
||||||
|
{
|
||||||
|
keep_link = true;
|
||||||
|
current_link.first = out.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// "rising edge" sets mode to ignore
|
||||||
|
if(in[i] == '<')
|
||||||
|
{
|
||||||
|
ignore = true;
|
||||||
|
}
|
||||||
|
std::string a = "</a>";
|
||||||
|
if( current_link.first != -1
|
||||||
|
&& last_six_chars.size() >= a.size()
|
||||||
|
&& last_six_chars.substr(last_six_chars.size()-a.size()) == a)
|
||||||
|
{
|
||||||
|
current_link.third = out.size();
|
||||||
|
links.push_back(current_link);
|
||||||
|
current_link = Triple();
|
||||||
|
}
|
||||||
|
if(!ignore || keep_link)
|
||||||
|
out += in[i];
|
||||||
|
// "falling edge" resets mode to keep
|
||||||
|
if(in[i] == '>')
|
||||||
|
ignore = false;
|
||||||
|
|
||||||
|
last_six_chars += in[i];
|
||||||
|
if(last_six_chars.size() > 6)
|
||||||
|
last_six_chars = last_six_chars.substr(1);
|
||||||
|
}
|
||||||
|
m.msg = out;
|
||||||
|
m.links = links;
|
||||||
|
m.recv_time = msg.recvTime;
|
||||||
|
m.send_time = msg.sendTime;
|
||||||
|
|
||||||
|
m.id = RSRandom::random_u32();
|
||||||
|
|
||||||
|
mMsgs[msg.chat_id].push_back(m);
|
||||||
|
done.push_back(lit);
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
for(std::vector<std::list<ChatMessage>::iterator>::iterator vit = done.begin(); vit != done.end(); ++vit)
|
||||||
|
mRawMsgs.erase(*vit);
|
||||||
|
|
||||||
|
// send changes
|
||||||
|
|
||||||
|
if(changed)
|
||||||
|
{
|
||||||
|
mStateTokenServer->replaceToken(mMsgStateToken);
|
||||||
|
mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(std::vector<RsPeerId>::iterator vit = peers_changed.begin(); vit != peers_changed.end(); ++vit)
|
||||||
|
{
|
||||||
|
const std::list<Msg>& msgs = mMsgs[ChatId(*vit)];
|
||||||
|
uint32_t count = 0;
|
||||||
|
for(std::list<Msg>::const_iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
|
||||||
|
if(!lit->read)
|
||||||
|
count++;
|
||||||
|
if(mUnreadMsgNotify)
|
||||||
|
mUnreadMsgNotify->notifyUnreadMsgCountChanged(*vit, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(lobbies.begin(), lobbies.end(), &compare_lobby_id);
|
||||||
|
if(lobby_unread_count_changed || mLobbies != lobbies)
|
||||||
|
{
|
||||||
|
mStateTokenServer->replaceToken(mLobbiesStateToken);
|
||||||
|
mLobbies = lobbies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleWildcard(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
resp.mDataStream.getStreamToMember();
|
||||||
|
for(std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.begin(); mit != mMsgs.end(); ++mit)
|
||||||
|
{
|
||||||
|
resp.mDataStream.getStreamToMember() << makeValue(mit->first.toStdString());
|
||||||
|
}
|
||||||
|
resp.setOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleLobbies(Request &/*req*/, Response &resp)
|
||||||
|
{
|
||||||
|
tick();
|
||||||
|
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
resp.mDataStream.getStreamToMember();
|
||||||
|
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
|
||||||
|
{
|
||||||
|
uint32_t unread_msgs = 0;
|
||||||
|
ChatId chat_id(vit->id);
|
||||||
|
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(chat_id);
|
||||||
|
if(mit != mMsgs.end())
|
||||||
|
{
|
||||||
|
std::list<Msg>& msgs = mit->second;
|
||||||
|
for(std::list<Msg>::iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
|
||||||
|
if(!lit->read)
|
||||||
|
unread_msgs++;
|
||||||
|
}
|
||||||
|
resp.mDataStream.getStreamToMember() << *vit << makeKeyValueReference("unread_msg_count", unread_msgs);
|
||||||
|
}
|
||||||
|
resp.mStateToken = mLobbiesStateToken;
|
||||||
|
resp.setOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleSubscribeLobby(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
ChatLobbyId id = 0;
|
||||||
|
RsGxsId gxs_id;
|
||||||
|
req.mStream << makeKeyValueReference("id", id) << makeKeyValueReference("gxs_id", gxs_id);
|
||||||
|
|
||||||
|
if(id == 0)
|
||||||
|
{
|
||||||
|
resp.setFail("Error: id must not be null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(gxs_id.isNull())
|
||||||
|
{
|
||||||
|
resp.setFail("Error: gxs_id must not be null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(mRsMsgs->joinVisibleChatLobby(id, gxs_id))
|
||||||
|
resp.setOk();
|
||||||
|
else
|
||||||
|
resp.setFail("lobby join failed. (See console for more info)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleUnsubscribeLobby(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
ChatLobbyId id = 0;
|
||||||
|
req.mStream << makeKeyValueReference("id", id);
|
||||||
|
mRsMsgs->unsubscribeChatLobby(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleMessages(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
ChatId id(req.mPath.top());
|
||||||
|
// make response a list
|
||||||
|
resp.mDataStream.getStreamToMember();
|
||||||
|
if(id.isNotSet())
|
||||||
|
{
|
||||||
|
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(id);
|
||||||
|
if(mit == mMsgs.end())
|
||||||
|
{
|
||||||
|
resp.mStateToken = mMsgStateToken; // even set state token, if not found yet, maybe later messages arrive and then the chat id will be found
|
||||||
|
resp.setFail("chat with id=\""+req.mPath.top()+"\" not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resp.mStateToken = mMsgStateToken;
|
||||||
|
handlePaginationRequest(req, resp, mit->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleSendMessage(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
std::string chat_id;
|
||||||
|
std::string msg;
|
||||||
|
req.mStream << makeKeyValueReference("chat_id", chat_id)
|
||||||
|
<< makeKeyValueReference("msg", msg);
|
||||||
|
ChatId id(chat_id);
|
||||||
|
if(id.isNotSet())
|
||||||
|
{
|
||||||
|
resp.setFail("chat_id is invalid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(mRsMsgs->sendChat(id, msg))
|
||||||
|
resp.setOk();
|
||||||
|
else
|
||||||
|
resp.setFail("failed to send message");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleMarkChatAsRead(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
ChatId id(req.mPath.top());
|
||||||
|
if(id.isNotSet())
|
||||||
|
{
|
||||||
|
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(id);
|
||||||
|
if(mit == mMsgs.end())
|
||||||
|
{
|
||||||
|
resp.setFail("chat not found. Maybe this chat does not have messages yet?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::list<Msg>& msgs = mit->second;
|
||||||
|
for(std::list<Msg>::iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
|
||||||
|
{
|
||||||
|
lit->read = true;
|
||||||
|
}
|
||||||
|
// lobby list contains unread msgs, so update it
|
||||||
|
if(id.isLobbyId())
|
||||||
|
mStateTokenServer->replaceToken(mLobbiesStateToken);
|
||||||
|
if(id.isPeerId() && mUnreadMsgNotify)
|
||||||
|
mUnreadMsgNotify->notifyUnreadMsgCountChanged(id.toPeerId(), 0);
|
||||||
|
|
||||||
|
mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// to be removed
|
||||||
|
// we do now cache chat info, to be able to include it in new message notify easily
|
||||||
|
/*
|
||||||
|
class InfoResponseTask: public GxsResponseTask
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InfoResponseTask(ChatHandler* ch, RsPeers* peers, RsIdentity* identity): GxsResponseTask(identity, 0), mChatHandler(ch), mRsPeers(peers), mState(BEGIN){}
|
||||||
|
|
||||||
|
enum State {BEGIN, WAITING};
|
||||||
|
ChatHandler* mChatHandler;
|
||||||
|
RsPeers* mRsPeers;
|
||||||
|
State mState;
|
||||||
|
bool is_broadcast;
|
||||||
|
bool is_gxs_id;
|
||||||
|
bool is_lobby;
|
||||||
|
bool is_peer;
|
||||||
|
std::string remote_author_id;
|
||||||
|
std::string remote_author_name;
|
||||||
|
virtual void gxsDoWork(Request& req, Response& resp)
|
||||||
|
{
|
||||||
|
ChatId id(req.mPath.top());
|
||||||
|
if(id.isNotSet())
|
||||||
|
{
|
||||||
|
resp.setFail("not a valid chat id");
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(mState == BEGIN)
|
||||||
|
{
|
||||||
|
is_broadcast = false;
|
||||||
|
is_gxs_id = false;
|
||||||
|
is_lobby = false;
|
||||||
|
is_peer = false;
|
||||||
|
if(id.isBroadcast())
|
||||||
|
{
|
||||||
|
is_broadcast = true;
|
||||||
|
}
|
||||||
|
else if(id.isGxsId())
|
||||||
|
{
|
||||||
|
is_gxs_id = true;
|
||||||
|
remote_author_id = id.toGxsId().toStdString();
|
||||||
|
requestGxsId(id.toGxsId());
|
||||||
|
}
|
||||||
|
else if(id.isLobbyId())
|
||||||
|
{
|
||||||
|
is_lobby = true;
|
||||||
|
remote_author_id = "";
|
||||||
|
remote_author_name = mChatHandler->getLobbyInfo(id.toLobbyId()).name;
|
||||||
|
}
|
||||||
|
else if(id.isPeerId())
|
||||||
|
{
|
||||||
|
is_peer = true;
|
||||||
|
remote_author_id = id.toPeerId().toStdString();
|
||||||
|
remote_author_name = mRsPeers->getPeerName(id.toPeerId());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Error in InfoResponseTask::gxsDoWork(): unhandled chat_id=" << id.toStdString() << std::endl;
|
||||||
|
}
|
||||||
|
mState = WAITING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(is_gxs_id)
|
||||||
|
remote_author_name = getName(id.toGxsId());
|
||||||
|
resp.mDataStream << makeKeyValueReference("remote_author_id", remote_author_id)
|
||||||
|
<< makeKeyValueReference("remote_author_name", remote_author_name)
|
||||||
|
<< makeKeyValueReference("is_broadcast", is_broadcast)
|
||||||
|
<< makeKeyValueReference("is_gxs_id", is_gxs_id)
|
||||||
|
<< makeKeyValueReference("is_lobby", is_lobby)
|
||||||
|
<< makeKeyValueReference("is_peer", is_peer);
|
||||||
|
resp.setOk();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ResponseTask *ChatHandler::handleInfo(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
return new InfoResponseTask(this, mRsPeers, mRsIdentity);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ChatHandler::handleInfo(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
ChatId id(req.mPath.top());
|
||||||
|
if(id.isNotSet())
|
||||||
|
{
|
||||||
|
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::map<ChatId, ChatInfo>::iterator mit = mChatInfo.find(id);
|
||||||
|
if(mit == mChatInfo.end())
|
||||||
|
{
|
||||||
|
resp.setFail("chat not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resp.mDataStream << mit->second;
|
||||||
|
resp.setOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleTypingLabel(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleSendStatus(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatHandler::handleUnreadMsgs(Request &req, Response &resp)
|
||||||
|
{
|
||||||
|
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||||
|
|
||||||
|
resp.mDataStream.getStreamToMember();
|
||||||
|
for(std::map<ChatId, std::list<Msg> >::const_iterator mit = mMsgs.begin(); mit != mMsgs.end(); ++mit)
|
||||||
|
{
|
||||||
|
uint32_t count = 0;
|
||||||
|
for(std::list<Msg>::const_iterator lit = mit->second.begin(); lit != mit->second.end(); ++lit)
|
||||||
|
if(!lit->read)
|
||||||
|
count++;
|
||||||
|
std::map<ChatId, ChatInfo>::iterator mit2 = mChatInfo.find(mit->first);
|
||||||
|
if(mit2 == mChatInfo.end())
|
||||||
|
std::cerr << "Error in ChatHandler::handleUnreadMsgs(): ChatInfo not found. It is weird if this happens. Normally it should not happen." << std::endl;
|
||||||
|
if(count && (mit2 != mChatInfo.end()))
|
||||||
|
{
|
||||||
|
resp.mDataStream.getStreamToMember()
|
||||||
|
<< makeKeyValue("id", mit->first.toStdString())
|
||||||
|
<< makeKeyValueReference("unread_count", count)
|
||||||
|
<< mit2->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.mStateToken = mUnreadMsgsStateToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace resource_api
|
||||||
|
@ -3,48 +3,124 @@
|
|||||||
#include "ResourceRouter.h"
|
#include "ResourceRouter.h"
|
||||||
#include "StateTokenServer.h"
|
#include "StateTokenServer.h"
|
||||||
#include <retroshare/rsnotify.h>
|
#include <retroshare/rsnotify.h>
|
||||||
|
#include <retroshare/rsmsgs.h>
|
||||||
|
|
||||||
class RsMsgs;
|
class RsPeers;
|
||||||
|
class RsIdentity;
|
||||||
|
|
||||||
namespace resource_api
|
namespace resource_api
|
||||||
{
|
{
|
||||||
|
|
||||||
class ChatHandler: public ResourceRouter, NotifyClient
|
class UnreadMsgNotify{
|
||||||
|
public:
|
||||||
|
virtual void notifyUnreadMsgCountChanged(const RsPeerId& peer, uint32_t count) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChatHandler: public ResourceRouter, NotifyClient, Tickable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ChatHandler(StateTokenServer* sts, RsNotify* notify, RsMsgs* msgs);
|
ChatHandler(StateTokenServer* sts, RsNotify* notify, RsMsgs* msgs, RsPeers* peers, RsIdentity *identity, UnreadMsgNotify* unread);
|
||||||
virtual ~ChatHandler();
|
virtual ~ChatHandler();
|
||||||
|
|
||||||
// from NotifyClient
|
// from NotifyClient
|
||||||
// note: this may get called from the own and from foreign threads
|
// note: this may get called from the own and from foreign threads
|
||||||
virtual void notifyChatMessage();
|
virtual void notifyChatMessage(const ChatMessage& msg);
|
||||||
|
|
||||||
private:
|
// from tickable
|
||||||
void handleWildcard(Request& req, Response& resp);
|
virtual void tick();
|
||||||
|
|
||||||
StateTokenServer* mStateTokenServer;
|
public:
|
||||||
RsNotify* mNotify;
|
class Triple
|
||||||
RsMsgs* mRsMsgs;
|
{
|
||||||
|
public:
|
||||||
RsMutex mMtx;
|
Triple(): first(-1), second(-1), third(-1){}
|
||||||
StateToken mStateToken; // mutex protected
|
double first;
|
||||||
|
double second;
|
||||||
// msgs flow like this:
|
double third;
|
||||||
// chatservice -> rawMsgs -> processedMsgs -> deletion
|
};
|
||||||
|
|
||||||
std::map<ChatId, std::list<ChatMessage> > mRawMsgs;
|
|
||||||
|
|
||||||
class Msg{
|
class Msg{
|
||||||
public:
|
public:
|
||||||
|
bool read;
|
||||||
bool incoming;
|
bool incoming;
|
||||||
bool was_send;
|
bool was_send;
|
||||||
//std::string chat_type;
|
//std::string chat_type;
|
||||||
std::string author_id; // peer or gxs id or "system" for system messages
|
std::string author_id; // peer or gxs id or "system" for system messages
|
||||||
std::string author_name;
|
std::string author_name;
|
||||||
std::string msg; // plain text only!
|
std::string msg; // plain text only!
|
||||||
|
std::vector<Triple> links;
|
||||||
|
uint32_t recv_time;
|
||||||
|
uint32_t send_time;
|
||||||
|
|
||||||
|
uint32_t id;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<ChatId, std::list<Msg> > mProcessedMsgs;
|
class Lobby{
|
||||||
|
public:
|
||||||
|
Lobby(): id(0), subscribed(false), auto_subscribe(false), is_private(false){}
|
||||||
|
ChatLobbyId id;
|
||||||
|
std::string name;
|
||||||
|
std::string topic;
|
||||||
|
bool subscribed;
|
||||||
|
bool auto_subscribe;
|
||||||
|
bool is_private;
|
||||||
|
|
||||||
|
RsGxsId gxs_id;// for subscribed lobbies: the id we use to write messages
|
||||||
|
|
||||||
|
bool operator==(const Lobby& l) const
|
||||||
|
{
|
||||||
|
return id == l.id
|
||||||
|
&& name == l.name
|
||||||
|
&& topic == l.topic
|
||||||
|
&& subscribed == l.subscribed
|
||||||
|
&& auto_subscribe == l.auto_subscribe
|
||||||
|
&& is_private == l.is_private
|
||||||
|
&& gxs_id == l.gxs_id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChatInfo{
|
||||||
|
public:
|
||||||
|
bool is_broadcast;
|
||||||
|
bool is_gxs_id;
|
||||||
|
bool is_lobby;
|
||||||
|
bool is_peer;
|
||||||
|
std::string remote_author_id;
|
||||||
|
std::string remote_author_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleWildcard(Request& req, Response& resp);
|
||||||
|
void handleLobbies(Request& req, Response& resp);
|
||||||
|
void handleSubscribeLobby(Request& req, Response& resp);
|
||||||
|
void handleUnsubscribeLobby(Request& req, Response& resp);
|
||||||
|
void handleMessages(Request& req, Response& resp);
|
||||||
|
void handleSendMessage(Request& req, Response& resp);
|
||||||
|
void handleMarkChatAsRead(Request& req, Response& resp);
|
||||||
|
void handleInfo(Request& req, Response& resp);
|
||||||
|
void handleTypingLabel(Request& req, Response& resp);
|
||||||
|
void handleSendStatus(Request& req, Response& resp);
|
||||||
|
void handleUnreadMsgs(Request& req, Response& resp);
|
||||||
|
|
||||||
|
StateTokenServer* mStateTokenServer;
|
||||||
|
RsNotify* mNotify;
|
||||||
|
RsMsgs* mRsMsgs;
|
||||||
|
RsPeers* mRsPeers;
|
||||||
|
RsIdentity* mRsIdentity;
|
||||||
|
UnreadMsgNotify* mUnreadMsgNotify;
|
||||||
|
|
||||||
|
RsMutex mMtx;
|
||||||
|
|
||||||
|
StateToken mMsgStateToken;
|
||||||
|
std::list<ChatMessage> mRawMsgs;
|
||||||
|
std::map<ChatId, std::list<Msg> > mMsgs;
|
||||||
|
|
||||||
|
std::map<ChatId, ChatInfo> mChatInfo;
|
||||||
|
|
||||||
|
StateToken mLobbiesStateToken;
|
||||||
|
std::vector<Lobby> mLobbies;
|
||||||
|
|
||||||
|
StateToken mUnreadMsgsStateToken;
|
||||||
|
|
||||||
};
|
};
|
||||||
} // namespace resource_api
|
} // namespace resource_api
|
||||||
|
@ -112,4 +112,17 @@ void GxsResponseTask::streamGxsId(RsGxsId id, StreamBase &stream)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GxsResponseTask::getName(RsGxsId id)
|
||||||
|
{
|
||||||
|
for(std::vector<RsIdentityDetails>::iterator vit = mIdentityDetails.begin();
|
||||||
|
vit != mIdentityDetails.end(); ++vit)
|
||||||
|
{
|
||||||
|
if(vit->mId == id)
|
||||||
|
return vit->mNickname;
|
||||||
|
}
|
||||||
|
std::cerr << "Warning: identity not found in GxsResponseTask::getName(). This is probably a bug. You must call GxsResponseTask::requestGxsId() before you can get the name." << std::endl;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace resource_api
|
} // namespace resource_api
|
||||||
|
@ -36,6 +36,7 @@ protected:
|
|||||||
void requestGxsId(RsGxsId id);
|
void requestGxsId(RsGxsId id);
|
||||||
// call stream function in the next cycle, then the names are available
|
// call stream function in the next cycle, then the names are available
|
||||||
void streamGxsId(RsGxsId id, StreamBase& stream);
|
void streamGxsId(RsGxsId id, StreamBase& stream);
|
||||||
|
std::string getName(RsGxsId id);
|
||||||
private:
|
private:
|
||||||
RsIdentity* mIdService;
|
RsIdentity* mIdService;
|
||||||
RsTokenService* mTokenService;
|
RsTokenService* mTokenService;
|
||||||
|
@ -127,8 +127,11 @@ void IdentityHandler::handleWildcard(Request &req, Response &resp)
|
|||||||
ResponseTask* IdentityHandler::handleOwn(Request &req, Response &resp)
|
ResponseTask* IdentityHandler::handleOwn(Request &req, Response &resp)
|
||||||
{
|
{
|
||||||
std::list<RsGxsId> ids;
|
std::list<RsGxsId> ids;
|
||||||
mRsIdentity->getOwnIds(ids);
|
if(mRsIdentity->getOwnIds(ids))
|
||||||
return new SendIdentitiesListTask(mRsIdentity, ids);
|
return new SendIdentitiesListTask(mRsIdentity, ids);
|
||||||
|
resp.mDataStream.getStreamToMember();
|
||||||
|
resp.setWarning("identities not loaded yet");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace resource_api
|
} // namespace resource_api
|
||||||
|
@ -11,13 +11,15 @@ namespace resource_api
|
|||||||
// the type returned by dereferencing the iterator should have a stream operator for StreamBase
|
// the type returned by dereferencing the iterator should have a stream operator for StreamBase
|
||||||
// the stream operator must not add an element "id", this is done by the pagination handler
|
// the stream operator must not add an element "id", this is done by the pagination handler
|
||||||
template<class C>
|
template<class C>
|
||||||
void handlePaginationRequest(Request& req, Response& resp, const C& data)
|
void handlePaginationRequest(Request& req, Response& resp, C& data)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
if(!req.isGet()){
|
if(!req.isGet()){
|
||||||
resp.mDebug << "unsupported method. only GET is allowed." << std::endl;
|
resp.mDebug << "unsupported method. only GET is allowed." << std::endl;
|
||||||
resp.setFail();
|
resp.setFail();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
if(data.begin() == data.end()){
|
if(data.begin() == data.end()){
|
||||||
// set result type to list
|
// set result type to list
|
||||||
resp.mDataStream.getStreamToMember();
|
resp.mDataStream.getStreamToMember();
|
||||||
@ -25,15 +27,16 @@ void handlePaginationRequest(Request& req, Response& resp, const C& data)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string first;
|
std::string begin_after;
|
||||||
std::string last;
|
std::string last;
|
||||||
req.mStream << makeKeyValueReference("first", first) << makeKeyValueReference("last", last);
|
req.mStream << makeKeyValueReference("begin_after", begin_after) << makeKeyValueReference("last", last);
|
||||||
|
|
||||||
C::iterator it_first = data.begin();
|
typename C::iterator it_first = data.begin();
|
||||||
if(first != "begin")
|
if(begin_after != "begin" && begin_after != "")
|
||||||
{
|
{
|
||||||
while(it_first != data.end() && id(*it_first) != first)
|
while(it_first != data.end() && id(*it_first) != begin_after)
|
||||||
it_first++;
|
it_first++;
|
||||||
|
it_first++; // get after the specified element
|
||||||
if(it_first == data.end())
|
if(it_first == data.end())
|
||||||
{
|
{
|
||||||
resp.setFail("Error: first id did not match any element");
|
resp.setFail("Error: first id did not match any element");
|
||||||
@ -41,8 +44,8 @@ void handlePaginationRequest(Request& req, Response& resp, const C& data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
C::iterator it_last = data.begin();
|
typename C::iterator it_last = data.begin();
|
||||||
if(last == "end")
|
if(last == "end" || last == "")
|
||||||
{
|
{
|
||||||
it_last = data.end();
|
it_last = data.end();
|
||||||
}
|
}
|
||||||
@ -59,7 +62,7 @@ void handlePaginationRequest(Request& req, Response& resp, const C& data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for(C::iterator it = it_first; it != it_last; ++it)
|
for(typename C::iterator it = it_first; it != it_last; ++it)
|
||||||
{
|
{
|
||||||
StreamBase& stream = resp.mDataStream.getStreamToMember();
|
StreamBase& stream = resp.mDataStream.getStreamToMember();
|
||||||
stream << *it;
|
stream << *it;
|
||||||
|
@ -28,7 +28,8 @@ bool peerInfoToStream(StreamBase& stream, RsPeerDetails& details, RsPeers* peers
|
|||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
peerDetailsToStream(stream, details);
|
peerDetailsToStream(stream, details);
|
||||||
stream << makeKeyValue("is_online", peers->isOnline(details.id));
|
stream << makeKeyValue("is_online", peers->isOnline(details.id))
|
||||||
|
<< makeKeyValue("chat_id", ChatId(details.id).toStdString());
|
||||||
|
|
||||||
std::string avatar_address = "/"+details.id.toStdString()+"/avatar_image";
|
std::string avatar_address = "/"+details.id.toStdString()+"/avatar_image";
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ void PeersHandler::tick()
|
|||||||
{
|
{
|
||||||
std::list<RsPeerId> online;
|
std::list<RsPeerId> online;
|
||||||
mRsPeers->getOnlineList(online);
|
mRsPeers->getOnlineList(online);
|
||||||
if(!std::equal(online.begin(), online.end(), mOnlinePeers.begin()))
|
if(online != mOnlinePeers)
|
||||||
{
|
{
|
||||||
mOnlinePeers = online;
|
mOnlinePeers = online;
|
||||||
|
|
||||||
@ -101,6 +102,13 @@ void PeersHandler::tick()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeersHandler::notifyUnreadMsgCountChanged(const RsPeerId &peer, uint32_t count)
|
||||||
|
{
|
||||||
|
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
|
||||||
|
mUnreadMsgsCounts[peer] = count;
|
||||||
|
mStateTokenServer->replaceToken(mStateToken);
|
||||||
|
}
|
||||||
|
|
||||||
static bool have_avatar(RsMsgs* msgs, const RsPeerId& id)
|
static bool have_avatar(RsMsgs* msgs, const RsPeerId& id)
|
||||||
{
|
{
|
||||||
// check if avatar data is available
|
// check if avatar data is available
|
||||||
@ -163,6 +171,11 @@ void PeersHandler::handleWildcard(Request &req, Response &resp)
|
|||||||
// no more path element
|
// no more path element
|
||||||
if(req.isGet())
|
if(req.isGet())
|
||||||
{
|
{
|
||||||
|
std::map<RsPeerId, uint32_t> unread_msgs;
|
||||||
|
{
|
||||||
|
RsStackMutex stack(mMtx); /********** STACK LOCKED MTX ******/
|
||||||
|
unread_msgs = mUnreadMsgsCounts;
|
||||||
|
}
|
||||||
// list all peers
|
// list all peers
|
||||||
ok = true;
|
ok = true;
|
||||||
std::list<RsPgpId> identities;
|
std::list<RsPgpId> identities;
|
||||||
@ -204,7 +217,14 @@ void PeersHandler::handleWildcard(Request &req, Response &resp)
|
|||||||
for(std::vector<RsPeerDetails>::iterator vit = detailsVec.begin(); vit != detailsVec.end(); ++vit)
|
for(std::vector<RsPeerDetails>::iterator vit = detailsVec.begin(); vit != detailsVec.end(); ++vit)
|
||||||
{
|
{
|
||||||
if(vit->gpg_id == *lit)
|
if(vit->gpg_id == *lit)
|
||||||
peerInfoToStream(locationStream.getStreamToMember(),*vit, mRsPeers, grpInfo, have_avatar(mRsMsgs, vit->id));
|
{
|
||||||
|
StreamBase& stream = locationStream.getStreamToMember();
|
||||||
|
double unread = 0;
|
||||||
|
if(unread_msgs.find(vit->id) != unread_msgs.end())
|
||||||
|
unread = unread_msgs.find(vit->id)->second;
|
||||||
|
stream << makeKeyValueReference("unread_msgs", unread);
|
||||||
|
peerInfoToStream(stream,*vit, mRsPeers, grpInfo, have_avatar(mRsMsgs, vit->id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.mStateToken = getCurrentStateToken();
|
resp.mStateToken = getCurrentStateToken();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "ResourceRouter.h"
|
#include "ResourceRouter.h"
|
||||||
#include "StateTokenServer.h"
|
#include "StateTokenServer.h"
|
||||||
|
#include "ChatHandler.h"
|
||||||
#include <retroshare/rsnotify.h>
|
#include <retroshare/rsnotify.h>
|
||||||
#include <util/rsthreads.h>
|
#include <util/rsthreads.h>
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ class RsMsgs;
|
|||||||
namespace resource_api
|
namespace resource_api
|
||||||
{
|
{
|
||||||
|
|
||||||
class PeersHandler: public ResourceRouter, NotifyClient, Tickable
|
class PeersHandler: public ResourceRouter, NotifyClient, Tickable, public UnreadMsgNotify
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers* peers, RsMsgs* msgs);
|
PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers* peers, RsMsgs* msgs);
|
||||||
@ -24,6 +25,12 @@ public:
|
|||||||
|
|
||||||
// from Tickable
|
// from Tickable
|
||||||
virtual void tick();
|
virtual void tick();
|
||||||
|
|
||||||
|
// from UnreadMsgNotify
|
||||||
|
// ChatHandler calls this to tell us about unreadmsgs
|
||||||
|
// this allows to merge unread msgs info with the peers list
|
||||||
|
virtual void notifyUnreadMsgCountChanged(const RsPeerId& peer, uint32_t count);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleWildcard(Request& req, Response& resp);
|
void handleWildcard(Request& req, Response& resp);
|
||||||
void handleExamineCert(Request& req, Response& resp);
|
void handleExamineCert(Request& req, Response& resp);
|
||||||
@ -40,5 +47,6 @@ private:
|
|||||||
|
|
||||||
RsMutex mMtx;
|
RsMutex mMtx;
|
||||||
StateToken mStateToken; // mutex protected
|
StateToken mStateToken; // mutex protected
|
||||||
|
std::map<RsPeerId, uint32_t> mUnreadMsgsCounts;
|
||||||
};
|
};
|
||||||
} // namespace resource_api
|
} // namespace resource_api
|
||||||
|
@ -98,7 +98,43 @@ var AutoUpdateMixin =
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// the signlaSlotServer decouples event senders from event receivers
|
// similar to auto update mixin, but for immutable resources
|
||||||
|
// fetches data only once
|
||||||
|
var OneTimeUpdateMixin =
|
||||||
|
{
|
||||||
|
// react component lifecycle callbacks
|
||||||
|
componentDidMount: function()
|
||||||
|
{
|
||||||
|
this._aum_debug("OneTimeUpdateMixin did mount path="+this.getPath());
|
||||||
|
this._aum_on_data_changed();
|
||||||
|
},
|
||||||
|
// private OneTimeUpdateMixin methods
|
||||||
|
_aum_debug: function(msg)
|
||||||
|
{
|
||||||
|
console.log(msg);
|
||||||
|
},
|
||||||
|
_aum_on_data_changed: function()
|
||||||
|
{
|
||||||
|
RS.request({path: this.getPath()}, this._aum_response_callback);
|
||||||
|
},
|
||||||
|
_aum_response_callback: function(resp)
|
||||||
|
{
|
||||||
|
this._aum_debug("OneTimeUpdateMixin received data: "+JSON.stringify(resp));
|
||||||
|
// it is impossible to update the state of an unmounted component
|
||||||
|
// but it may happen that the component is unmounted before a request finishes
|
||||||
|
// if response is too late, we drop it
|
||||||
|
if(!this.isMounted())
|
||||||
|
{
|
||||||
|
this._aum_debug("OneTimeUpdateMixin: component not mounted. Discarding response. path="+this.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var state = this.state;
|
||||||
|
state.data = resp.data;
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// the signalSlotServer decouples event senders from event receivers
|
||||||
// senders just send their events
|
// senders just send their events
|
||||||
// the server will forwards them to all receivers
|
// the server will forwards them to all receivers
|
||||||
// receivers have to register/unregister at the server
|
// receivers have to register/unregister at the server
|
||||||
@ -311,7 +347,12 @@ var Peers3 = React.createClass({
|
|||||||
};
|
};
|
||||||
if(loc.is_online)
|
if(loc.is_online)
|
||||||
online_style.backgroundColor = "lime";
|
online_style.backgroundColor = "lime";
|
||||||
return(<div key={loc.peer_id} style={{color: loc.is_online? "lime": "grey"}}>{/*<div style={online_style}></div>*/}{loc.location}</div>);
|
//console.log(loc);
|
||||||
|
return(<div key={loc.peer_id}
|
||||||
|
style={{color: loc.is_online? "lime": "grey", cursor: loc.is_online ? "pointer": "auto"}}
|
||||||
|
onClick={function(){component.emit("change_url", {url: "chat/"+loc.chat_id});}}>
|
||||||
|
{/*<div style={online_style}></div>*/}{loc.location} {loc.unread_msgs !== 0? ("("+loc.unread_msgs + " unread msgs)"): ""}
|
||||||
|
</div>);
|
||||||
});
|
});
|
||||||
var avatars = this.props.data.locations.map(function(loc){
|
var avatars = this.props.data.locations.map(function(loc){
|
||||||
if(loc.is_online && (loc.avatar_address !== ""))
|
if(loc.is_online && (loc.avatar_address !== ""))
|
||||||
@ -978,7 +1019,7 @@ var LoginWidget2 = React.createClass({
|
|||||||
if(this.state.state === "waiting")
|
if(this.state.state === "waiting")
|
||||||
{
|
{
|
||||||
return(<div>
|
return(<div>
|
||||||
<p>please wait a second...</p>
|
<p>please wait a second... (LoginWidget2)</p>
|
||||||
</div>);
|
</div>);
|
||||||
//return(<p>Retroshare is initialising... please wait...</p>);
|
//return(<p>Retroshare is initialising... please wait...</p>);
|
||||||
}
|
}
|
||||||
@ -1070,6 +1111,12 @@ var Menu = React.createClass({
|
|||||||
},
|
},
|
||||||
render: function(){
|
render: function(){
|
||||||
var outer = this;
|
var outer = this;
|
||||||
|
var shutdownbutton;
|
||||||
|
if(this.props.fullcontrol === true)
|
||||||
|
shutdownbutton = <div onClick={function(){RS.request({path: "control/shutdown"});}} className="btn2">shutdown Retroshare</div>;
|
||||||
|
else
|
||||||
|
shutdownbutton = <div></div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
|
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
|
||||||
@ -1091,6 +1138,10 @@ var Menu = React.createClass({
|
|||||||
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "search"});}}>
|
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "search"});}}>
|
||||||
Search
|
Search
|
||||||
</div>
|
</div>
|
||||||
|
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "lobbies"});}}>
|
||||||
|
Chatlobbies (unfinished)
|
||||||
|
</div>
|
||||||
|
{shutdownbutton}
|
||||||
{/*<div className="btn2" onClick={function(){outer.emit("change_url", {url: "testwidget"});}}>
|
{/*<div className="btn2" onClick={function(){outer.emit("change_url", {url: "testwidget"});}}>
|
||||||
TestWidget
|
TestWidget
|
||||||
</div>*/}
|
</div>*/}
|
||||||
@ -1099,6 +1150,331 @@ var Menu = React.createClass({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var global_author_identity = null;
|
||||||
|
|
||||||
|
var IdentitySelectorWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "identity/own";
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
if(this.state.data.length !== 0)
|
||||||
|
{
|
||||||
|
global_author_identity = this.state.data[0].gxs_id;
|
||||||
|
return <div>using identity {this.state.data[0].name}</div>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return <div>error: no identity found. create_new_identity not implemented. try a page reload.</div>;
|
||||||
|
}
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.data.map(function(id){
|
||||||
|
return <div className="btn2" key={id.id} onClick ={function(){}}>{id.name}</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var LobbyListWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin, SignalSlotMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/lobbies";
|
||||||
|
},
|
||||||
|
enterLobby: function(id, chat_id)
|
||||||
|
{
|
||||||
|
var c = this;
|
||||||
|
if(global_author_identity === null)
|
||||||
|
{
|
||||||
|
alert("no identity selected, can't join a lobby");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RS.request(
|
||||||
|
{path:"chat/subscribe_lobby", data:{id:id, gxs_id:global_author_identity}},
|
||||||
|
function(){
|
||||||
|
c.emit("change_url", {url: "chat/"+chat_id});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.data.map(function(lobby){
|
||||||
|
return <div className="btn2" key={lobby.id} onClick ={function(){c.enterLobby(lobby.id, lobby.chat_id);}}>{lobby.name}<br/>{lobby.topic}<br/>{lobby.unread_msg_count + " unread msgs"}</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// implements automatic update using the state token system
|
||||||
|
// components using this mixin should have a member "getPath()" to specify the resource
|
||||||
|
// this widget handles paginated resources
|
||||||
|
var ListAutoUpdateMixin =
|
||||||
|
{
|
||||||
|
// react component lifecycle callbacks
|
||||||
|
componentDidMount: function()
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin did mount path="+this.getPath());
|
||||||
|
this._aum_on_data_changed();
|
||||||
|
},
|
||||||
|
componentWillUnmount: function()
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin will unmount path="+this.getPath());
|
||||||
|
RS.unregister_token_listener(this._aum_on_data_changed);
|
||||||
|
},
|
||||||
|
|
||||||
|
// private auto update mixin methods
|
||||||
|
_aum_debug: function(msg)
|
||||||
|
{
|
||||||
|
console.log(msg);
|
||||||
|
},
|
||||||
|
_aum_on_data_changed: function()
|
||||||
|
{
|
||||||
|
if(this.state.data.length === 0)
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: first request");
|
||||||
|
RS.request({path: this.getPath()}, this._aum_response_callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: requesting elements after id="+this._aum_last_id);
|
||||||
|
RS.request({path: this.getPath(), data: {begin_after: this.state.data[this.state.data.length-1].id}}, this._aum_response_callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_aum_response_callback: function(resp)
|
||||||
|
{
|
||||||
|
this._aum_debug("Mixin received data: "+JSON.stringify(resp));
|
||||||
|
// it is impossible to update the state of an unmounted component
|
||||||
|
// but it may happen that the component is unmounted before a request finishes
|
||||||
|
// if response is too late, we drop it
|
||||||
|
if(!this.isMounted())
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: component not mounted. Discarding response. path="+this.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var state = this.state;
|
||||||
|
if(state.data.length === 0)
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: received first response");
|
||||||
|
state.data = resp.data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: appending response to the end");
|
||||||
|
state.data = state.data.concat(resp.data);
|
||||||
|
if(resp.data.length !== 0)
|
||||||
|
this._aum_last_id = resp.data[resp.data.length-1].id;
|
||||||
|
}
|
||||||
|
if(this.onDataUpdated)
|
||||||
|
this.onDataUpdated();
|
||||||
|
this.setState(state);
|
||||||
|
// load mroe data until there is no more data left
|
||||||
|
if(resp.data.length === 0)
|
||||||
|
{
|
||||||
|
RS.unregister_token_listener(this._aum_on_data_changed);
|
||||||
|
RS.register_token_listener(this._aum_on_data_changed, resp.statetoken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._aum_on_data_changed();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getChatTypeString(info)
|
||||||
|
{
|
||||||
|
if(info.is_broadcast)
|
||||||
|
return "[broadcast]";
|
||||||
|
if(info.is_gxs_id)
|
||||||
|
return "[distant]";
|
||||||
|
if(info.is_lobby)
|
||||||
|
return "[lobby]";
|
||||||
|
if(info.is_peer)
|
||||||
|
return "[friend]";
|
||||||
|
return "[unknown chat type]";
|
||||||
|
}
|
||||||
|
|
||||||
|
var ChatInfoWidget = React.createClass({
|
||||||
|
mixins: [OneTimeUpdateMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: null};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/info/"+this.props.id;
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
if(this.state.data === null)
|
||||||
|
return <div></div>
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{getChatTypeString(this.state.data)} {this.state.data.remote_author_name}
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ChatWidget sets this, and the unread msgs widget ignores unread smgs with this id
|
||||||
|
var global_current_chat_id = null;
|
||||||
|
|
||||||
|
var UnreadChatMsgsCountWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin, SignalSlotMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/unread_msgs";
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
var count = 0;
|
||||||
|
for(var i in this.state.data)
|
||||||
|
{
|
||||||
|
if(this.state.data[i].id !== global_current_chat_id)
|
||||||
|
count = count + parseInt(this.state.data[i].unread_count);
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<div style={{cursor: "pointer"}}
|
||||||
|
onClick={function(){c.emit("change_url", {url: "unread_chats"});}}
|
||||||
|
>
|
||||||
|
{count !== 0? (count + " unread chat messages. click here to read them."): ""}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var UnreadChatMsgsListWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin, SignalSlotMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/unread_msgs";
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<div>Unread Chats:</div>
|
||||||
|
{this.state.data.map(function(i){
|
||||||
|
return <div className="btn2" key={i.id} onClick={function(){c.emit("change_url", {url: "chat/"+i.id});}}>
|
||||||
|
{getChatTypeString(i)} {"["+i.unread_count+"]"} {i.remote_author_name}
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var LinkWidget = React.createClass({
|
||||||
|
getInitialState: function(){
|
||||||
|
return {expanded: false};
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
if(this.state.expanded){
|
||||||
|
return <div>Really follow this link? <a href={this.props.url}>{this.props.url}</a> <div onClick={function(){c.setState({expanded: false});}}>close</div></div>;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return <a onClick={function(e){c.setState({expanded: true});e.stopPropagation();}} href={this.props.url}>{this.props.label}</a>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// text: a string
|
||||||
|
// links: [{first:1, second:2, third:3}]
|
||||||
|
// text between first and second is the url of the link
|
||||||
|
// text between secon and third is the link label
|
||||||
|
function markup(text, links)
|
||||||
|
{
|
||||||
|
function debug(stuff){console.log(stuff)}
|
||||||
|
var out = [];
|
||||||
|
var last_link = 0;
|
||||||
|
debug(text);
|
||||||
|
debug(links);
|
||||||
|
for(var i in links)
|
||||||
|
{
|
||||||
|
out.push(text.substr(last_link, links[i].first).replace(/ /g, " "));
|
||||||
|
var url = text.substr(links[i].first, links[i].second);
|
||||||
|
url = url.replace(/&/g, "&");
|
||||||
|
var label = text.substr(links[i].second, links[i].third);
|
||||||
|
label = label.replace(/ /g, " ");
|
||||||
|
last_link = links[i].third;
|
||||||
|
debug(links[i]);
|
||||||
|
debug(url);
|
||||||
|
debug(label);
|
||||||
|
out.push(<LinkWidget url={url} label={label}/>);
|
||||||
|
}
|
||||||
|
out.push(text.substr(last_link).replace(/ /g, " "));
|
||||||
|
debug(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ChatWidget = React.createClass({
|
||||||
|
mixins: [ListAutoUpdateMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/messages/"+this.props.id;
|
||||||
|
},
|
||||||
|
onDataUpdated: function()
|
||||||
|
{
|
||||||
|
RS.request({path:"chat/mark_chat_as_read/"+this.props.id});
|
||||||
|
},
|
||||||
|
// react component lifecycle callbacks
|
||||||
|
componentDidMount: function()
|
||||||
|
{
|
||||||
|
global_current_chat_id = this.props.id;
|
||||||
|
},
|
||||||
|
componentWillUnmount: function()
|
||||||
|
{
|
||||||
|
global_current_chat_id = null;
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<ChatInfoWidget id={this.props.id}/>
|
||||||
|
{
|
||||||
|
this.state.data.map(function(msg){
|
||||||
|
return <div key={msg.id}>{msg.send_time} {msg.author_name}: {markup(msg.msg, msg.links)}</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<input type="text" ref="input" onKeyDown={
|
||||||
|
function(event){
|
||||||
|
if(event.keyCode == 13){
|
||||||
|
RS.request({
|
||||||
|
path:"chat/send_message",
|
||||||
|
data:{
|
||||||
|
chat_id: c.props.id,
|
||||||
|
msg: c.refs.input.getDOMNode().value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// hack: directly mofify the DOM node
|
||||||
|
c.refs.input.getDOMNode().value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
var TestWidget = React.createClass({
|
var TestWidget = React.createClass({
|
||||||
mixins: [SignalSlotMixin],
|
mixins: [SignalSlotMixin],
|
||||||
getInitialState: function(){
|
getInitialState: function(){
|
||||||
@ -1242,6 +1618,9 @@ var MainWidget = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function(){
|
render: function(){
|
||||||
|
console.log("MainWidget render()");
|
||||||
|
console.log(this.state);
|
||||||
|
|
||||||
var outer = this;
|
var outer = this;
|
||||||
var mainpage = <p>page not implemented: {this.state.page}</p>;
|
var mainpage = <p>page not implemented: {this.state.page}</p>;
|
||||||
|
|
||||||
@ -1286,6 +1665,18 @@ var MainWidget = React.createClass({
|
|||||||
{
|
{
|
||||||
mainpage = <SearchWidget/>;
|
mainpage = <SearchWidget/>;
|
||||||
}
|
}
|
||||||
|
if(this.state.page === "lobbies")
|
||||||
|
{
|
||||||
|
mainpage = <LobbyListWidget/>;
|
||||||
|
}
|
||||||
|
if(this.state.page.split("/")[0] === "chat")
|
||||||
|
{
|
||||||
|
mainpage = <ChatWidget id={this.state.page.split("/")[1]}/>;
|
||||||
|
}
|
||||||
|
if(this.state.page === "unread_chats")
|
||||||
|
{
|
||||||
|
mainpage = <UnreadChatMsgsListWidget/>;
|
||||||
|
}
|
||||||
if(this.state.page === "add_friend")
|
if(this.state.page === "add_friend")
|
||||||
{
|
{
|
||||||
mainpage = <AddPeerWidget/>;
|
mainpage = <AddPeerWidget/>;
|
||||||
@ -1294,6 +1685,12 @@ var MainWidget = React.createClass({
|
|||||||
{
|
{
|
||||||
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
|
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
|
||||||
}
|
}
|
||||||
|
mainpage = <div>
|
||||||
|
<UnreadChatMsgsCountWidget/>
|
||||||
|
<AudioPlayerWidget/>
|
||||||
|
<IdentitySelectorWidget/>
|
||||||
|
{mainpage}
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2"><- menu</div>;
|
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2"><- menu</div>;
|
||||||
@ -1304,7 +1701,6 @@ var MainWidget = React.createClass({
|
|||||||
{/*<div id="overlay"><div className="paddingbox"><div className="btn2">test</div></div></div>*/}
|
{/*<div id="overlay"><div className="paddingbox"><div className="btn2">test</div></div></div>*/}
|
||||||
<ConnectionStatusWidget/>
|
<ConnectionStatusWidget/>
|
||||||
<PasswordWidget/>
|
<PasswordWidget/>
|
||||||
<AudioPlayerWidget/>
|
|
||||||
{menubutton}
|
{menubutton}
|
||||||
{mainpage}
|
{mainpage}
|
||||||
{/*<ProgressBar progress={0.7}/>*/}
|
{/*<ProgressBar progress={0.7}/>*/}
|
||||||
|
@ -98,7 +98,43 @@ var AutoUpdateMixin =
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// the signlaSlotServer decouples event senders from event receivers
|
// similar to auto update mixin, but for immutable resources
|
||||||
|
// fetches data only once
|
||||||
|
var OneTimeUpdateMixin =
|
||||||
|
{
|
||||||
|
// react component lifecycle callbacks
|
||||||
|
componentDidMount: function()
|
||||||
|
{
|
||||||
|
this._aum_debug("OneTimeUpdateMixin did mount path="+this.getPath());
|
||||||
|
this._aum_on_data_changed();
|
||||||
|
},
|
||||||
|
// private OneTimeUpdateMixin methods
|
||||||
|
_aum_debug: function(msg)
|
||||||
|
{
|
||||||
|
console.log(msg);
|
||||||
|
},
|
||||||
|
_aum_on_data_changed: function()
|
||||||
|
{
|
||||||
|
RS.request({path: this.getPath()}, this._aum_response_callback);
|
||||||
|
},
|
||||||
|
_aum_response_callback: function(resp)
|
||||||
|
{
|
||||||
|
this._aum_debug("OneTimeUpdateMixin received data: "+JSON.stringify(resp));
|
||||||
|
// it is impossible to update the state of an unmounted component
|
||||||
|
// but it may happen that the component is unmounted before a request finishes
|
||||||
|
// if response is too late, we drop it
|
||||||
|
if(!this.isMounted())
|
||||||
|
{
|
||||||
|
this._aum_debug("OneTimeUpdateMixin: component not mounted. Discarding response. path="+this.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var state = this.state;
|
||||||
|
state.data = resp.data;
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// the signalSlotServer decouples event senders from event receivers
|
||||||
// senders just send their events
|
// senders just send their events
|
||||||
// the server will forwards them to all receivers
|
// the server will forwards them to all receivers
|
||||||
// receivers have to register/unregister at the server
|
// receivers have to register/unregister at the server
|
||||||
@ -311,7 +347,12 @@ var Peers3 = React.createClass({
|
|||||||
};
|
};
|
||||||
if(loc.is_online)
|
if(loc.is_online)
|
||||||
online_style.backgroundColor = "lime";
|
online_style.backgroundColor = "lime";
|
||||||
return(<div key={loc.peer_id} style={{color: loc.is_online? "lime": "grey"}}>{/*<div style={online_style}></div>*/}{loc.location}</div>);
|
//console.log(loc);
|
||||||
|
return(<div key={loc.peer_id}
|
||||||
|
style={{color: loc.is_online? "lime": "grey", cursor: loc.is_online ? "pointer": "auto"}}
|
||||||
|
onClick={function(){component.emit("change_url", {url: "chat/"+loc.chat_id});}}>
|
||||||
|
{/*<div style={online_style}></div>*/}{loc.location} {loc.unread_msgs !== 0? ("("+loc.unread_msgs + " unread msgs)"): ""}
|
||||||
|
</div>);
|
||||||
});
|
});
|
||||||
var avatars = this.props.data.locations.map(function(loc){
|
var avatars = this.props.data.locations.map(function(loc){
|
||||||
if(loc.is_online && (loc.avatar_address !== ""))
|
if(loc.is_online && (loc.avatar_address !== ""))
|
||||||
@ -978,7 +1019,7 @@ var LoginWidget2 = React.createClass({
|
|||||||
if(this.state.state === "waiting")
|
if(this.state.state === "waiting")
|
||||||
{
|
{
|
||||||
return(<div>
|
return(<div>
|
||||||
<p>please wait a second...</p>
|
<p>please wait a second... (LoginWidget2)</p>
|
||||||
</div>);
|
</div>);
|
||||||
//return(<p>Retroshare is initialising... please wait...</p>);
|
//return(<p>Retroshare is initialising... please wait...</p>);
|
||||||
}
|
}
|
||||||
@ -1070,6 +1111,12 @@ var Menu = React.createClass({
|
|||||||
},
|
},
|
||||||
render: function(){
|
render: function(){
|
||||||
var outer = this;
|
var outer = this;
|
||||||
|
var shutdownbutton;
|
||||||
|
if(this.props.fullcontrol === true)
|
||||||
|
shutdownbutton = <div onClick={function(){RS.request({path: "control/shutdown"});}} className="btn2">shutdown Retroshare</div>;
|
||||||
|
else
|
||||||
|
shutdownbutton = <div></div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
|
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
|
||||||
@ -1091,6 +1138,10 @@ var Menu = React.createClass({
|
|||||||
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "search"});}}>
|
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "search"});}}>
|
||||||
Search
|
Search
|
||||||
</div>
|
</div>
|
||||||
|
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "lobbies"});}}>
|
||||||
|
Chatlobbies (unfinished)
|
||||||
|
</div>
|
||||||
|
{shutdownbutton}
|
||||||
{/*<div className="btn2" onClick={function(){outer.emit("change_url", {url: "testwidget"});}}>
|
{/*<div className="btn2" onClick={function(){outer.emit("change_url", {url: "testwidget"});}}>
|
||||||
TestWidget
|
TestWidget
|
||||||
</div>*/}
|
</div>*/}
|
||||||
@ -1099,6 +1150,331 @@ var Menu = React.createClass({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var global_author_identity = null;
|
||||||
|
|
||||||
|
var IdentitySelectorWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "identity/own";
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
if(this.state.data.length !== 0)
|
||||||
|
{
|
||||||
|
global_author_identity = this.state.data[0].gxs_id;
|
||||||
|
return <div>using identity {this.state.data[0].name}</div>;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return <div>error: no identity found. create_new_identity not implemented. try a page reload.</div>;
|
||||||
|
}
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.data.map(function(id){
|
||||||
|
return <div className="btn2" key={id.id} onClick ={function(){}}>{id.name}</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var LobbyListWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin, SignalSlotMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/lobbies";
|
||||||
|
},
|
||||||
|
enterLobby: function(id, chat_id)
|
||||||
|
{
|
||||||
|
var c = this;
|
||||||
|
if(global_author_identity === null)
|
||||||
|
{
|
||||||
|
alert("no identity selected, can't join a lobby");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RS.request(
|
||||||
|
{path:"chat/subscribe_lobby", data:{id:id, gxs_id:global_author_identity}},
|
||||||
|
function(){
|
||||||
|
c.emit("change_url", {url: "chat/"+chat_id});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.data.map(function(lobby){
|
||||||
|
return <div className="btn2" key={lobby.id} onClick ={function(){c.enterLobby(lobby.id, lobby.chat_id);}}>{lobby.name}<br/>{lobby.topic}<br/>{lobby.unread_msg_count + " unread msgs"}</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// implements automatic update using the state token system
|
||||||
|
// components using this mixin should have a member "getPath()" to specify the resource
|
||||||
|
// this widget handles paginated resources
|
||||||
|
var ListAutoUpdateMixin =
|
||||||
|
{
|
||||||
|
// react component lifecycle callbacks
|
||||||
|
componentDidMount: function()
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin did mount path="+this.getPath());
|
||||||
|
this._aum_on_data_changed();
|
||||||
|
},
|
||||||
|
componentWillUnmount: function()
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin will unmount path="+this.getPath());
|
||||||
|
RS.unregister_token_listener(this._aum_on_data_changed);
|
||||||
|
},
|
||||||
|
|
||||||
|
// private auto update mixin methods
|
||||||
|
_aum_debug: function(msg)
|
||||||
|
{
|
||||||
|
console.log(msg);
|
||||||
|
},
|
||||||
|
_aum_on_data_changed: function()
|
||||||
|
{
|
||||||
|
if(this.state.data.length === 0)
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: first request");
|
||||||
|
RS.request({path: this.getPath()}, this._aum_response_callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: requesting elements after id="+this._aum_last_id);
|
||||||
|
RS.request({path: this.getPath(), data: {begin_after: this.state.data[this.state.data.length-1].id}}, this._aum_response_callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_aum_response_callback: function(resp)
|
||||||
|
{
|
||||||
|
this._aum_debug("Mixin received data: "+JSON.stringify(resp));
|
||||||
|
// it is impossible to update the state of an unmounted component
|
||||||
|
// but it may happen that the component is unmounted before a request finishes
|
||||||
|
// if response is too late, we drop it
|
||||||
|
if(!this.isMounted())
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: component not mounted. Discarding response. path="+this.getPath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var state = this.state;
|
||||||
|
if(state.data.length === 0)
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: received first response");
|
||||||
|
state.data = resp.data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._aum_debug("ListAutoUpdateMixin: appending response to the end");
|
||||||
|
state.data = state.data.concat(resp.data);
|
||||||
|
if(resp.data.length !== 0)
|
||||||
|
this._aum_last_id = resp.data[resp.data.length-1].id;
|
||||||
|
}
|
||||||
|
if(this.onDataUpdated)
|
||||||
|
this.onDataUpdated();
|
||||||
|
this.setState(state);
|
||||||
|
// load mroe data until there is no more data left
|
||||||
|
if(resp.data.length === 0)
|
||||||
|
{
|
||||||
|
RS.unregister_token_listener(this._aum_on_data_changed);
|
||||||
|
RS.register_token_listener(this._aum_on_data_changed, resp.statetoken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._aum_on_data_changed();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getChatTypeString(info)
|
||||||
|
{
|
||||||
|
if(info.is_broadcast)
|
||||||
|
return "[broadcast]";
|
||||||
|
if(info.is_gxs_id)
|
||||||
|
return "[distant]";
|
||||||
|
if(info.is_lobby)
|
||||||
|
return "[lobby]";
|
||||||
|
if(info.is_peer)
|
||||||
|
return "[friend]";
|
||||||
|
return "[unknown chat type]";
|
||||||
|
}
|
||||||
|
|
||||||
|
var ChatInfoWidget = React.createClass({
|
||||||
|
mixins: [OneTimeUpdateMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: null};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/info/"+this.props.id;
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
if(this.state.data === null)
|
||||||
|
return <div></div>
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
{getChatTypeString(this.state.data)} {this.state.data.remote_author_name}
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ChatWidget sets this, and the unread msgs widget ignores unread smgs with this id
|
||||||
|
var global_current_chat_id = null;
|
||||||
|
|
||||||
|
var UnreadChatMsgsCountWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin, SignalSlotMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/unread_msgs";
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
var count = 0;
|
||||||
|
for(var i in this.state.data)
|
||||||
|
{
|
||||||
|
if(this.state.data[i].id !== global_current_chat_id)
|
||||||
|
count = count + parseInt(this.state.data[i].unread_count);
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<div style={{cursor: "pointer"}}
|
||||||
|
onClick={function(){c.emit("change_url", {url: "unread_chats"});}}
|
||||||
|
>
|
||||||
|
{count !== 0? (count + " unread chat messages. click here to read them."): ""}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var UnreadChatMsgsListWidget = React.createClass({
|
||||||
|
mixins: [AutoUpdateMixin, SignalSlotMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/unread_msgs";
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<div>Unread Chats:</div>
|
||||||
|
{this.state.data.map(function(i){
|
||||||
|
return <div className="btn2" key={i.id} onClick={function(){c.emit("change_url", {url: "chat/"+i.id});}}>
|
||||||
|
{getChatTypeString(i)} {"["+i.unread_count+"]"} {i.remote_author_name}
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var LinkWidget = React.createClass({
|
||||||
|
getInitialState: function(){
|
||||||
|
return {expanded: false};
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
if(this.state.expanded){
|
||||||
|
return <div>Really follow this link? <a href={this.props.url}>{this.props.url}</a> <div onClick={function(){c.setState({expanded: false});}}>close</div></div>;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return <a onClick={function(e){c.setState({expanded: true});e.stopPropagation();}} href={this.props.url}>{this.props.label}</a>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// text: a string
|
||||||
|
// links: [{first:1, second:2, third:3}]
|
||||||
|
// text between first and second is the url of the link
|
||||||
|
// text between secon and third is the link label
|
||||||
|
function markup(text, links)
|
||||||
|
{
|
||||||
|
function debug(stuff){console.log(stuff)}
|
||||||
|
var out = [];
|
||||||
|
var last_link = 0;
|
||||||
|
debug(text);
|
||||||
|
debug(links);
|
||||||
|
for(var i in links)
|
||||||
|
{
|
||||||
|
out.push(text.substr(last_link, links[i].first).replace(/ /g, " "));
|
||||||
|
var url = text.substr(links[i].first, links[i].second);
|
||||||
|
url = url.replace(/&/g, "&");
|
||||||
|
var label = text.substr(links[i].second, links[i].third);
|
||||||
|
label = label.replace(/ /g, " ");
|
||||||
|
last_link = links[i].third;
|
||||||
|
debug(links[i]);
|
||||||
|
debug(url);
|
||||||
|
debug(label);
|
||||||
|
out.push(<LinkWidget url={url} label={label}/>);
|
||||||
|
}
|
||||||
|
out.push(text.substr(last_link).replace(/ /g, " "));
|
||||||
|
debug(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ChatWidget = React.createClass({
|
||||||
|
mixins: [ListAutoUpdateMixin],
|
||||||
|
getInitialState: function(){
|
||||||
|
return {data: []};
|
||||||
|
},
|
||||||
|
getPath: function(){
|
||||||
|
return "chat/messages/"+this.props.id;
|
||||||
|
},
|
||||||
|
onDataUpdated: function()
|
||||||
|
{
|
||||||
|
RS.request({path:"chat/mark_chat_as_read/"+this.props.id});
|
||||||
|
},
|
||||||
|
// react component lifecycle callbacks
|
||||||
|
componentDidMount: function()
|
||||||
|
{
|
||||||
|
global_current_chat_id = this.props.id;
|
||||||
|
},
|
||||||
|
componentWillUnmount: function()
|
||||||
|
{
|
||||||
|
global_current_chat_id = null;
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
var c = this;
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<ChatInfoWidget id={this.props.id}/>
|
||||||
|
{
|
||||||
|
this.state.data.map(function(msg){
|
||||||
|
return <div key={msg.id}>{msg.send_time} {msg.author_name}: {markup(msg.msg, msg.links)}</div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<input type="text" ref="input" onKeyDown={
|
||||||
|
function(event){
|
||||||
|
if(event.keyCode == 13){
|
||||||
|
RS.request({
|
||||||
|
path:"chat/send_message",
|
||||||
|
data:{
|
||||||
|
chat_id: c.props.id,
|
||||||
|
msg: c.refs.input.getDOMNode().value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// hack: directly mofify the DOM node
|
||||||
|
c.refs.input.getDOMNode().value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
var TestWidget = React.createClass({
|
var TestWidget = React.createClass({
|
||||||
mixins: [SignalSlotMixin],
|
mixins: [SignalSlotMixin],
|
||||||
getInitialState: function(){
|
getInitialState: function(){
|
||||||
@ -1242,6 +1618,9 @@ var MainWidget = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function(){
|
render: function(){
|
||||||
|
console.log("MainWidget render()");
|
||||||
|
console.log(this.state);
|
||||||
|
|
||||||
var outer = this;
|
var outer = this;
|
||||||
var mainpage = <p>page not implemented: {this.state.page}</p>;
|
var mainpage = <p>page not implemented: {this.state.page}</p>;
|
||||||
|
|
||||||
@ -1286,6 +1665,18 @@ var MainWidget = React.createClass({
|
|||||||
{
|
{
|
||||||
mainpage = <SearchWidget/>;
|
mainpage = <SearchWidget/>;
|
||||||
}
|
}
|
||||||
|
if(this.state.page === "lobbies")
|
||||||
|
{
|
||||||
|
mainpage = <LobbyListWidget/>;
|
||||||
|
}
|
||||||
|
if(this.state.page.split("/")[0] === "chat")
|
||||||
|
{
|
||||||
|
mainpage = <ChatWidget id={this.state.page.split("/")[1]}/>;
|
||||||
|
}
|
||||||
|
if(this.state.page === "unread_chats")
|
||||||
|
{
|
||||||
|
mainpage = <UnreadChatMsgsListWidget/>;
|
||||||
|
}
|
||||||
if(this.state.page === "add_friend")
|
if(this.state.page === "add_friend")
|
||||||
{
|
{
|
||||||
mainpage = <AddPeerWidget/>;
|
mainpage = <AddPeerWidget/>;
|
||||||
@ -1294,6 +1685,12 @@ var MainWidget = React.createClass({
|
|||||||
{
|
{
|
||||||
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
|
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
|
||||||
}
|
}
|
||||||
|
mainpage = <div>
|
||||||
|
<UnreadChatMsgsCountWidget/>
|
||||||
|
<AudioPlayerWidget/>
|
||||||
|
<IdentitySelectorWidget/>
|
||||||
|
{mainpage}
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2"><- menu</div>;
|
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2"><- menu</div>;
|
||||||
@ -1304,7 +1701,6 @@ var MainWidget = React.createClass({
|
|||||||
{/*<div id="overlay"><div className="paddingbox"><div className="btn2">test</div></div></div>*/}
|
{/*<div id="overlay"><div className="paddingbox"><div className="btn2">test</div></div></div>*/}
|
||||||
<ConnectionStatusWidget/>
|
<ConnectionStatusWidget/>
|
||||||
<PasswordWidget/>
|
<PasswordWidget/>
|
||||||
<AudioPlayerWidget/>
|
|
||||||
{menubutton}
|
{menubutton}
|
||||||
{mainpage}
|
{mainpage}
|
||||||
{/*<ProgressBar progress={0.7}/>*/}
|
{/*<ProgressBar progress={0.7}/>*/}
|
||||||
|
@ -117,7 +117,7 @@ QString WebuiPage::helpText() const
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QMessageBox::warning(0, tr("Webinterface not enabled"), "The webinterface is not enabled. Enable it in Settings -> Webinterface.");
|
QMessageBox::warning(0, tr("Webinterface not enabled"), tr("The webinterface is not enabled. Enable it in Settings -> Webinterface."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user