--- /dev/null
+// I have no idea if this is good form or not
+var Dispatcher = {
+ send_msg: function(destination, method, params) {
+ var m = {
+ "destination": destination,
+ "method": method,
+ "params": params
+ };
+ window.ws_conn.send(JSON.stringify(m));
+ }
+};
+
+window.Dispatcher = Dispatcher;
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" href="style.css">
+ <!-- The core React library, plus some experimental addons -->
+ <script src="http://fb.me/react-with-addons-0.10.0.js"></script>
+ <!-- In-browser JSX transformer, remove when pre-compiling JSX. -->
+ <script src="http://fb.me/JSXTransformer-0.10.0.js"></script>
+ <script type="text/jsx" src="widgets.js"></script>
+ <script type="text/javascript" src="dispatcher.js"></script>
+ <script type="text/javascript" src="main.js"></script>
+ </head>
+ <body>
+ <!-- A div for the initial login form -->
+ <div id="loginform"></div>
+ <!-- A div for the buddy list -->
+ <div id="blist"></div>
+ <!-- A div for the tabbed chat view -->
+ <div id="chats"></div>
+ </body>
+</html>
--- /dev/null
+// this is like a main.js
+
+window.blist = {}
+
+window.onload = function () {
+ // Create websocket and attach to window
+ var ws = new WebSocket("ws://localhost:8889/ws");
+
+ ws.onmessage = function(evt) {
+ //console.log("message received: " + evt.data);
+ var message = JSON.parse(evt.data)
+ console.log(message);
+ };
+ ws.onclose = function(evt) {
+ console.log("connection closed");
+ };
+ ws.onopen = function(evt) {
+ console.log("opened");
+ };
+ window.ws_conn = ws;
+
+ // Attach React-rendered components
+ React.renderComponent(
+ window.widgets.LoginForm(),
+ document.getElementById('loginform')
+ );
+
+ window.blist = React.renderComponent(
+ window.widgets.BuddyList(),
+ document.getElementById('blist')
+ );
+ console.log("loaded");
+};
--- /dev/null
+import json
+import logging
+import os
+import socket
+import struct
+import subprocess
+
+import tornado.httpserver
+import tornado.ioloop
+import tornado.web
+import tornado.websocket
+
+pwd = os.path.dirname(os.path.abspath(__file__))
+
+logging.basicConfig(
+ level=logging.DEBUG,
+ format='%(asctime)s : %(levelname)s : %(name)s : %(message)s',
+ )
+
+log = logging.getLogger()
+
+class WSHandler(tornado.websocket.WebSocketHandler):
+ def on_ribbon_length_received(self, data):
+ length = struct.unpack(">i", data)[0]
+ log.info("ribbon sent length {}".format(length))
+ self.ribbon_stream.read_bytes(length, self.on_ribbon_message)
+
+ def on_ribbon_message(self, message):
+ log.info("ribbon sent data {}".format(message))
+ # Dispatch it straight to the frontend; #nofilter
+ self.write_message(message)
+ # And prepare for the next message
+ self.ribbon_stream.read_bytes(4, self.on_ribbon_length_received)
+
+ def on_ribbon_ready(self):
+ log.info("ribbon ready")
+ self.ribbon_ready = True
+ self.dispatch_backlog()
+ # Set up reads to trigger the length callback
+ self.ribbon_stream.read_bytes(4, self.on_ribbon_length_received)
+
+ def dispatch_backlog(self):
+ log.info("dispatch backlog")
+ for message in self.backlog:
+ log.info("write {}".format(message))
+ length = struct.pack(">I", len(message))
+ self.ribbon_stream.write(length)
+ self.ribbon_stream.write(message)
+ self.backlog = []
+
+ def queue_message(self, message):
+ self.backlog.append(message)
+ if self.ribbon_ready:
+ self.dispatch_backlog()
+
+ def open(self):
+ # Create a new connection to ribbon.
+ self.ribbon_ready = False
+ self.backlog = [] # A list of bytestrings queued to be sent on the socket.
+ self.ribbon_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ self.ribbon_stream = tornado.iostream.IOStream(self.ribbon_socket)
+ self.ribbon_stream.connect( ("localhost", 8888), self.on_ribbon_ready)
+ log.info('new connection')
+ self.write_message('{"key":"value"}')
+ def on_message(self, message):
+ log.info('message received: %r', message)
+ data = json.loads(message)
+ self.queue_message(message.encode("latin1"))
+
+ def on_close(self):
+ log.info('connection closed')
+
+
+application = tornado.web.Application([
+ (r'/ws', WSHandler),
+])
+
+
+if __name__ == "__main__":
+ # Spawn the ribbon subprocess
+ ribbon_binary = os.path.join(os.path.dirname(pwd), "ribbon", "ribbon")
+ ribbon = subprocess.Popen( [ribbon_binary], close_fds=True )
+
+ # Spawn the http server
+ http_server = tornado.httpserver.HTTPServer(application)
+ http_server.listen(8889)
+ log.info("starting up")
+ tornado.ioloop.IOLoop.instance().start()
--- /dev/null
+.form {
+ float: left;
+}
+
+.form > label {
+ display: block;
+}
+.form > label > input {
+ display: block;
+ margin-bottom: 20px;
+}
+
+#blist {
+ float: right;
+}
+
+/* buddy list things */
+.blistitem {
+ width: 200px;
+ height: 48px;
+ font-size: small;
+}
+
+.blistitem > img {
+ display: inline-block;
+ width: 48px;
+ height: 48px;
+ float: left;
+}
+
+.blistitemcontainer {
+ display: inline-block;
+ vertical-align: top;
+ width: 152px;
+}
+
+.blist-buddyname {
+ font-weight: bold;
+}
+
+.blisttext {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ width: 100%;
+}
--- /dev/null
+/** @jsx React.DOM */
+
+// Testing stuff
+var List = React.createClass({
+ render: function () {
+ var items = this.props.items;
+ var listitems = [];
+ for (var i = 0 ; i < items.length ; i++) {
+ listitems.push(<ListItem key={i}>{items[i]}</ListItem>);
+ }
+ return <ul>{listitems}</ul>;
+ }
+});
+
+var ListItem = React.createClass({
+ render: function () {
+ return <li>{this.props.children}</li>;
+ }
+});
+
+
+// Login form
+
+var LoginForm = React.createClass({
+ mixins: [React.addons.LinkedStateMixin],
+ getInitialState: function () {
+ return {
+ "protocol": "prpl-jabber",
+ "username": "",
+ "password": ""
+ };
+ },
+ handleLogin: function () {
+ console.log("Imma log in now");
+ command = {
+ "destination": "accounts",
+ "method": "create_account",
+ "params": this.state
+ };
+ window.ws_conn.send(JSON.stringify(command));
+ },
+
+ render: function () {
+ return (
+ <form className="form">
+ <label>Chat Network:<select name="chatnetwork" valueLink={this.linkState('protocol')}>
+ <option value="prpl-jabber">Jabber</option>
+ <option value="prpl-aim">AIM</option>
+ </select></label>
+ <label>Username:<input type="text" id="username" valueLink={this.linkState('username')} /></label>
+ <label>Password:<input type="password" id="password" valueLink={this.linkState('password')} /></label>
+ <input type="button" id="login" value="Login" onClick={this.handleLogin} />
+ </form>
+ );
+ }
+});
+
+
+// Buddy list stuff
+
+var BuddyListItem = React.createClass({
+ render: function () {
+ return <div className="blistitem">
+ <img className="buddyicon" src={this.props.bicon_url} />
+ <div className="blistitemcontainer">
+ <div className="blist-buddyname blisttext">{this.props.display_name}</div>
+ <div className="blisttext">{this.props.status}</div>
+ </div>
+ </div>;
+ }
+});
+
+var BuddyList = React.createClass({
+ getInitialState: function () {
+ return {"buddies": []};
+ },
+ onChange: function(newData) {
+ this.setState( { "buddies": newData } );
+ },
+ render: function () {
+ // for each thing in this.state:
+ // render thing
+ var things = [];
+ for (var i = 0 ; i < this.state.buddies.length ; i++) {
+ var s = this.state.buddies[i];
+ things.push(BuddyListItem(this.state.buddies[i]));
+ }
+ return <div className="blist">{ things }</div>;
+ }
+});
+
+// integration for now:
+window.widgets = {
+ "List": List,
+ "ListItem": ListItem,
+ "BuddyListItem": BuddyListItem,
+ "LoginForm": LoginForm,
+ "BuddyList": BuddyList
+};