Compare commits

..

No commits in common. "master" and "logging" have entirely different histories.

24 changed files with 73 additions and 1392 deletions

View File

@ -1,4 +1,4 @@
Meshenger meshenger
========= =========
Meshenger is a Forban-inspired messaging software used for a speculative broadcast communication project. The starting point is an electronic messaging system running on a wireless mesh network. The messages propagate through the network when devices that come in contact with each other synchronize their content. It is non-hierarchical, every node receives, relays and broadcasts messages. Meshenger is a Forban-inspired messaging software used for a speculative broadcast communication project. The starting point is an electronic messaging system running on a wireless mesh network. The messages propagate through the network when devices that come in contact with each other synchronize their content. It is non-hierarchical, every node receives, relays and broadcasts messages.
@ -26,18 +26,6 @@ You are going to need to have an internet connection to your router, the easiest
Alternatively if you use OSX you can enable internet sharing (make sure to set your OSX machine as the gateway and DNS server for your router in /etc/config/network) Alternatively if you use OSX you can enable internet sharing (make sure to set your OSX machine as the gateway and DNS server for your router in /etc/config/network)
*If after flashing something goes wrong, you can reset the whole router in failsafe mode* see [here](http://wiki.openwrt.org/doc/howto/generic.failsafe).
### Lazy Install
Below you will find the manual configuration steps necessary to install Meshenger. We did however create some scripts to help you automate the process.
Read more [here](https://github.com/rscmbbng/meshenger/blob/master/lazyinstall/README.md)
To add: stop Luci from running!
`$ /etc/init.d/uhttpd disable`
Saves a lot of resources!
### System configuration ### System configuration
To use your router for Meshenger you're going to need to run the whole filesystem from a USB-Drive. To use your router for Meshenger you're going to need to run the whole filesystem from a USB-Drive.

View File

@ -1,46 +1,3 @@
This is a very primitive script to automate the openwrt setup. This is a very primitive script to automate the openwrt setup.
It's probably best not used at this point. It's probably best not used at this point.
However if you choose to use it make sure you have the following things set up:
- The OpenWRT router has a connection to the internet (by attaching it to another router via ethernet)
- You have a properly formatted usb drive (one ext4 partition, one 32mb swap partiton)
- Make sure the usb flash drive is inserted in the router
The lazy install scripts works by moving config files from this folder to replace the ones on the target router's filesystem.
Therefore you need to edit the following files to suit your own needs:
network file:
lines 11 and 13, the ip adress you want to use plus the ip adress of the gateway that provides this router with its internet connection.
wireless file:
line 11,the wireless SSID that the router will get
Once you've set all of this up copy the lazyinstall directory to your open-wrt router:
scp -r /path/to/meshenger/lazyinstall/ root@target.router.ip.adress:~/
ssh into the target router:
ssh root@target.router.ip.address
navigate to where you copied the lazyinstall directory:
cd ~/lazyinstall
first execute lazyinstall1, it will set up the ext_root on ths usb drive
./lazyinstall1.sh
once that's done reboot the router:
reboot -f
once it is up again, ssh back in. the router has now booted from the external usb drive. you can verify that by doing:
df -h
which should show you that rootfs is the size of your external usb drive
run lazyinstall2. this will copy all the config files, download python and git and clone the meshenger project. This can take a while.
After this is done you can reboot and you will have a fully functioning meshenger node!

View File

@ -17,7 +17,7 @@ mount -t ext4 /dev/sda1 /mnt/sda1
echo 'Cleaning USB drive' echo 'Cleaning USB drive'
rm -r /mnt/sda1/* rm -r /mnt/sda1/
sleep 4 sleep 4
@ -25,7 +25,7 @@ echo 'Copying filesystem to USB drive'
mkdir -p /tmp/cproot mkdir -p /tmp/cproot
mount --bind / /tmp/cproot mount --bind / /tmp/cproot
sleep 1 sleep 1
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1/ -xf - tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1 -xf -
sleep 1 sleep 1
umount /tmp/cproot umount /tmp/cproot

View File

@ -32,7 +32,7 @@ sed -i -e "s/option 'interfaces' 'mesh'/option 'interfaces' 'adhoc0'/g" /etc/con
opkg install python git opkg install python git
sleep 1 sleep 1
git clone git://github.com/rscmbbng/meshenger /root/meshenger git clone ://github.com/rscmbbng/meshenger /root/meshenger
mv uhttpd /etc/config/uhttpd mv uhttpd /etc/config/uhttpd

View File

@ -7,7 +7,7 @@ config wifi-iface 'wmesh'
option bssid '66:66:66:66:66:66' option bssid '66:66:66:66:66:66'
config wifi-iface config wifi-iface
option 'device' 'radio0' #use your existing wifi device, look in the list above. option 'device' 'radio0' #use your excisting wifi device, look in the list above.
option 'ssid' 'meshenger_node' #use a unique name for your network? option 'ssid' 'meshenger_node' #use a unique name for your network?
option 'network' 'hotspot' option 'network' 'hotspot'
option 'mode' 'ap' option 'mode' 'ap'

125
main.py
View File

@ -2,39 +2,31 @@
import socket, os, time, select, urllib, sys, threading, json, logging, logging.config import socket, os, time, select, urllib, sys, threading, json, logging, logging.config
os.chdir(os.path.dirname(__file__)) # change present working directory to the one where this file is
logging.config.fileConfig('pylog.conf') logging.config.fileConfig('pylog.conf')
logger = logging.getLogger('meshenger'+'.main') logger = logging.getLogger('meshenger'+'.main')
class Meshenger: class Meshenger:
devices = {} #the dictionary of all the nodes this this node has seen. each ip-entry has a tuple of 'foreign index update time' and time when last seen devices = {} #the dictionary of all the nodes this this node has seen
devices_old = {}
serve_port = "13338" serve_port = "13338"
announce_port = 13337 announce_port = 13337
#own_ip = "0.0.0.0" #own_ip = "0.0.0.0"
msg_dir = os.path.relpath('msg/') msg_dir = os.path.relpath('msg/')
exitapp = False #to kill all threads on exitapp = False #to kill all threads on
index_last_update = str(int(time.time())) index_last_update = str(int(time.time()))
node_expiry = 15 #the time to wait before removing a node form the discovered nodelist
def __init__(self): def __init__(self):
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-lan/disable_ipv6") os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-lan/disable_ipv6")
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-hotspot/disable_ipv6") os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-hotspot/disable_ipv6")
os.chdir(os.path.dirname(__file__)) # change present working directory to the one where this file is
self.own_ip = self.get_ip_adress().strip() self.own_ip = self.get_ip_adress().strip()
# this hash is needed in clientserve, so client can generate color
self.own_hash = self.hasj(self.own_ip)
if not os.path.exists(self.msg_dir): if not os.path.exists(self.msg_dir):
os.mkdir(self.msg_dir) os.mkdir(self.msg_dir)
logger.info('Making message directory') logger.info('Making message directory')
self.init_index()
self.make_alias()
try: try:
d = threading.Thread(target=self.discover) d = threading.Thread(target=self.discover)
d.daemon = True d.daemon = True
@ -52,6 +44,12 @@ class Meshenger:
c.daemon = True c.daemon = True
c.start() c.start()
b = threading.Thread(target=self.build_index)
b.daemon = True
b.start()
#os.system("python meshenger_clientserve.py")
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
logger.info('exiting discovery thread') logger.info('exiting discovery thread')
d.join() d.join()
@ -60,10 +58,6 @@ class Meshenger:
n.join() n.join()
c.join() c.join()
sys.exit() sys.exit()
except Exception as e:
logger.warning( 'Main __init__ thread exception: %s', repr(e) )
except:
logger.warning( 'Main __init__ unknown thread exception')
while True: while True:
logger.debug('Entering main loop') logger.debug('Entering main loop')
@ -73,7 +67,7 @@ class Meshenger:
for device in self.devices.keys(): for device in self.devices.keys():
nodehash = self.hasj(device) nodehash = self.hasj(device)
nodepath = os.path.join(os.path.abspath('nodes'), nodehash) nodepath = os.path.join(os.path.abspath('nodes'), nodehash)
nodeupdatepath = os.path.join(nodepath, 'lastupdate') nodeupdatepath = os.path.join(nodepath, 'lastupdate')
logger.info('Checking age of foreign node index') logger.info('Checking age of foreign node index')
@ -91,27 +85,15 @@ class Meshenger:
self.get_index(device, nodepath) self.get_index(device, nodepath)
logger.info('downloading messages') logger.info('downloading messages')
self.get_messages(device, nodepath, nodehash) self.get_messages(device, nodepath, nodehash)
self.build_index()
self.node_timestamp(device) self.node_timestamp(device)
self.devices_old = dict(self.devices)
#check to see if a node has been missing for a while, if so remove it from self.devices
for device in self.devices_old.keys():
update_time = int(self.devices[device][1])
time_delta = int(time.time())- update_time
if time_delta >= self.node_expiry:
logger.info('Node '+device+' missing for '+str(self.node_expiry)+' seconds. Removing from list')
del self.devices[device]
time.sleep(5) #free process or ctrl+c time.sleep(5) #free process or ctrl+c
def node_timestamp(self, ip): def node_timestamp(self, ip):
nodepath = os.path.abspath(os.path.join('nodes', self.hasj(ip))) nodepath = os.path.abspath(os.path.join('nodes', self.hasj(ip)))
updatepath = os.path.join(nodepath, 'lastupdate') updatepath = os.path.join(nodepath, 'lastupdate')
with open(updatepath, 'wb') as lastupdate: with open(updatepath, 'wb') as lastupdate:
lastupdate.write(self.devices[ip][0]) lastupdate.write(self.devices[ip])
#return updatepath #return updatepath
@ -122,20 +104,17 @@ Announce the node's existance to other nodes
""" """
logger.info('Announcing') logger.info('Announcing')
while not self.exitapp: while not self.exitapp:
try: sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port)) sock.close()
#sock.close() time.sleep(5)
time.sleep(5)
except:
logger.warning('Failed to announce myself!')
def discover(self): def discover(self):
""" """
Discover other devices by listening to the Meshenger announce port Discover other devices by listening to the Meshenger announce port
""" """
logger.info('Discovering') print 'Discovering'
bufferSize = 1024 # whatever you need? bufferSize = 1024 # whatever you need?
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
@ -144,19 +123,19 @@ Discover other devices by listening to the Meshenger announce port
while not self.exitapp: while not self.exitapp:
result = select.select([s],[],[])[0][0].recvfrom(bufferSize) result = select.select([s],[],[])[0][0].recvfrom(bufferSize)
ip = result[1][0] ip = result[1][0]
#logger.debug('%s %s', ip, 'discovered') logger.info('%s %s', ip, "*"*45)
node_path = os.path.join(os.path.abspath('nodes'), self.hasj(ip)) node_path = os.path.join(os.path.abspath('nodes'), self.hasj(ip))
announce_time = str(int(time.time()))
if not os.path.exists(node_path) and ip != self.own_ip: if not os.path.exists(node_path) and ip != self.own_ip:
#loop for first time #loop for first time
self.ip_to_hash_path(ip) #make a folder /nodes/hash self.ip_to_hash_path(ip) #make a folder /nodes/hash
self.devices[ip] = result[0], announce_time self.devices[ip] = result[0]
#self.node_timestamp(ip) #make a local copy of the timestamp in /nodes/hash/updatetimestamp #self.node_timestamp(ip) #make a local copy of the timestamp in /nodes/hash/updatetimestamp
logger.info('New node %s', ip) logger.info('New node %s', ip)
elif os.path.exists(node_path) and ip != self.own_ip: elif os.path.exists(node_path) and ip != self.own_ip:
logger.info('Known node %s', ip) logger.info('Known node %s', ip)
self.devices[ip] = result[0], announce_time self.devices[ip] = result[0]
time.sleep(1) time.sleep(1)
@ -175,64 +154,45 @@ Initialize the clientserver
""" """
logger.info('Serving to client') logger.info('Serving to client')
import meshenger_clientserve import meshenger_clientserve
# set a reference to this object
meshenger_clientserve.meshenger = self
meshenger_clientserve.main() meshenger_clientserve.main()
# meshenger_clientserve.build_index_callback = self.build_index
def init_index(self):
def build_index(self):
""" """
Initialize the index. Read from disk or create new. Make an index file of all the messages present on the node.
Save the time of the last update.
""" """
logger.info('Checking own index for the first time\n')
logger.info('Building own index for the first time\n')
if not os.path.exists('index'): if not os.path.exists('index'):
with open('index','wb') as index: with open('index','wb') as index:
index.write('') index.write('')
self.previous_index = [] previous_index = []
else: else:
self.previous_index = open('index').readlines() previous_index = open('index').readlines()
self.build_index()
def build_index(self): while not self.exitapp:
""" current_index = os.listdir(self.msg_dir)
Make an index file of all the messages present on the node. if current_index != previous_index:
Save the time of the last update. with open('index', 'wb') as index:
""" for message in os.listdir(self.msg_dir):
logger.debug('build_index') index.write(message)
current_index = os.listdir(self.msg_dir) index.write('\n')
if current_index != self.previous_index: self.index_last_update = str(int(time.time()))
with open('index', 'wb') as index:
for message in os.listdir(self.msg_dir):
index.write(message)
index.write('\n')
self.index_last_update = str(int(time.time()))
logger.info('Index updated: %s', current_index) logger.info('Index updated: %s', current_index)
with open('index_last_update', 'wb') as indexupdate: with open('index_last_update', 'wb') as indexupdate:
indexupdate.write(self.index_last_update) ### misschien moet dit index_last_update zijn indexupdate.write(self.index_last_update) ### misschien moet dit index_last_update zijn
self.previous_index = current_index
def make_alias(self):
"""
See if the node already has an alias (nickname) if not just use the IP-Hash
"""
if not os.path.exists('alias'):
with open('alias','wb') as alias:
self.alias = self.own_hash
alias.write(self.own_hash)
else:
self.alias = open('alias','rb').read().replace('\n','') #should we replace the newline?
pass
logger.debug('Alias is "'+self.alias+'"')
previous_index = current_index
time.sleep(5)
def get_index(self,ip, path): def get_index(self,ip, path):
""" """
Download the indices from other nodes. Download the indices from other nodes.
""" """
os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index')) os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index'))
@ -285,7 +245,6 @@ Convert a node's ip into a hash and make a directory to store it's files
""" """
Hack to adhoc0's inet6 adress Hack to adhoc0's inet6 adress
""" """
#TODO possibly check if it's an empty file
if not os.path.isfile('interfaceip6adress'): if not os.path.isfile('interfaceip6adress'):
os.system('ifconfig -a adhoc0 | grep inet6 > /root/meshenger/interfaceip6adress') os.system('ifconfig -a adhoc0 | grep inet6 > /root/meshenger/interfaceip6adress')
with open('/root/meshenger/interfaceip6adress', 'r') as a: with open('/root/meshenger/interfaceip6adress', 'r') as a:

View File

@ -13,34 +13,27 @@ import logging, logging.config
logging.config.fileConfig('pylog.conf') logging.config.fileConfig('pylog.conf')
logger = logging.getLogger('meshenger'+'.clientserve') logger = logging.getLogger('meshenger'+'.clientserve')
class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
messageDir = "msg" messageDir = "msg"
"""
Serve index and messages
"""
def do_GET(self): def do_GET(self):
"""
Serve index and messages
"""
if self.path == '/index' or self.path.startswith( "/"+self.messageDir ): if self.path == '/':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
f = os.path.relpath('webapp.html')
# f = os.path.join('/root/meshenger/',"webapp.html")
with open( f, 'r') as the_file:
self.wfile.write(the_file.read())
elif self.path == '/index' or self.path.startswith( "/"+self.messageDir ):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
elif self.path == '/id':
if meshenger and meshenger.own_hash:
self.send_response(200)
self.send_header('Content-type', 'text-html')
self.end_headers()
self.wfile.write(meshenger.own_hash)
else:
self.send_error(404,'Id not yet available')
elif self.path == '/alias':
if meshenger and meshenger.alias:
self.send_response(200)
self.send_header('Content-type', 'text-html')
self.end_headers()
self.wfile.write(meshenger.alias)
else:
self.send_error(404,'Alias not yet available')
elif self.path == '/log': elif self.path == '/log':
self.send_response(200) self.send_response(200)
self.send_header('Content-type', 'text-html') self.send_header('Content-type', 'text-html')
@ -48,33 +41,21 @@ Serve index and messages
f = os.path.relpath('log/meshenger.log') f = os.path.relpath('log/meshenger.log')
with open( f, 'r') as the_file: with open( f, 'r') as the_file:
self.wfile.write(the_file.read()) self.wfile.write(the_file.read())
elif self.path.startswith('/web'): else:
f = os.path.relpath(self.path[1:])
if not os.path.exists( f ) or os.path.isdir( f ):
f = os.path.join('web', 'index.html')
self.path = '/'+f
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
elif self.path == '/old':
self.send_response(200) #serve the webapp on every url requested self.send_response(200) #serve the webapp on every url requested
self.send_header('Content-type', 'text/html') self.send_header('Content-type', 'text/html')
self.end_headers() self.end_headers()
f = os.path.relpath( 'webapp.html') f = os.path.join( "webapp.html")
with open( f, 'r') as the_file: with open( f, 'r') as the_file:
self.wfile.write(the_file.read()) self.wfile.write(the_file.read())
else:
self.path = '/' + os.path.join('web', 'index.html')
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
"""
Allow clients to post messages
"""
def do_POST(self): def do_POST(self):
"""
Allow clients to post messages
"""
if self.path == '/send': if self.path == '/send':
logger.info('incoming message from client!')
length = int(self.headers['Content-Length']) length = int(self.headers['Content-Length'])
post_data = urlparse.parse_qs(self.rfile.read(length).decode('utf-8')) post_data = urlparse.parse_qs(self.rfile.read(length).decode('utf-8'))
@ -85,18 +66,8 @@ Allow clients to post messages
self.end_headers() self.end_headers()
self.wfile.write('message created') self.wfile.write('message created')
#let main rebuild message index
try:
logger.debug('try to call meshenger.build_index: %s', repr(meshenger))
meshenger.build_index()
except:
logger.error('failed to call meshenger.build_index')
pass
def writeMessage(self, time, message): def writeMessage(self, time, message):
"""
Write message to disk
"""
f = os.path.join( self.messageDir, time) f = os.path.join( self.messageDir, time)
if os.path.isfile( f ): if os.path.isfile( f ):
return return
@ -106,7 +77,6 @@ Write message to disk
class ClientServe(): class ClientServe():
def __init__(self, port): def __init__(self, port):
logger.info('ClientServe.__init__')
server = HTTPServer( ('', port), ClientServeHandler) server = HTTPServer( ('', port), ClientServeHandler)
server.serve_forever() server.serve_forever()

View File

@ -28,20 +28,16 @@ class NodeServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
messageDir = "/msg" messageDir = "/msg"
"""
Serve index and messages
"""
def do_GET(self): def do_GET(self):
"""
Serve index and messages
"""
if self.path == '/': if self.path == '/':
self.path = "/index" self.path = "/index"
if self.path == '/index' or self.path.startswith( self.messageDir ): if self.path == '/index' or self.path.startswith( self.messageDir ):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
if self.path == '/alias' or self.path.startswith( self.messageDir ):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
else: else:
self.send_response(404) self.send_response(404)
self.send_header('Content-type', 'text/html') self.send_header('Content-type', 'text/html')
@ -53,7 +49,6 @@ class HTTPServerV6(BaseHTTPServer.HTTPServer):
class NodeServe(): class NodeServe():
def __init__(self, port): def __init__(self, port):
logger.info('NodeServe.__init__')
server = HTTPServerV6(('::', port), NodeServeHandler) server = HTTPServerV6(('::', port), NodeServeHandler)
server.serve_forever() server.serve_forever()

Binary file not shown.

View File

@ -1,45 +0,0 @@
function parseEmoticons( inputStr ){
var emoticons = [
{
text: ':)',
image: '1.png'
},
{
text: '<iframe',
image: 'iframe-open.png'
},
{
text: '</iframe>',
image: 'iframe-close.png'
},
{
text: '<script',
image: 'script-open.gif'
},
{
text: '</script>',
image: 'script-close.gif'
},
{
text: '<style',
image: 'style-open.png'
},
{
text: '</style>',
image: 'style-close.png'
},
{
text:';)',
image: '2.png'
},
{
text:':rock:',
image: '3.png'
}
];
for ( var i = 0; i < emoticons.length; i++ ){
inputStr = inputStr.split(emoticons[i].text).join('<img class="emo" src="/web/emoticons/'+emoticons[i].image+'">');
}
return inputStr;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,70 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="viewport" content="width=device-width, user-scalable=no">
<link href="/web/style.css" rel="stylesheet" type="text/css">
<title>meshenger</title>
</head>
<body>
<div id="message-page">
<button id="message-back" class="back-button">Back</button><br><br>
<form id="message-form" action="" accept-charset="utf-8">
<textarea id="name" rows="1" placeholder="Your Name"></textarea>
<textarea id="message" rows="2" placeholder="Your Message"></textarea>
<br>
<input type="submit" class="send-button" value="Send">
</form>
</div>
<div id="photo-page" class="photo-page">
<button id="photo-back" class="back-button">Back</button><br>
<input id="fileInput" accept="image/*" capture="camera" type="file">
<br>
<p>
<span id="progress">--</span>
<span id="total">--</span>
</p>
<!--Squashed source<br>-->
<img id="sourceImage" src="" alt="capture" height="200" width="200">
<!--Offset done<br>-->
<canvas id="canvas1" width="200" height="200"></canvas>
<!--Dithered<br>-->
<canvas id="canvas2" width="200" height="200"></canvas>
<!--Rotated<br>-->
<canvas id="canvas3" width="200" height="200"></canvas>
<br>
<button id="rot-left" class="rot-left">Rotate left</button>
<button id="rot-right" class="rot-right">Rotate right</button>
<br><br>
<textarea id="photo-name" rows="1" placeholder="Your Name"></textarea>
<br>
<button id="submit-photo" class="photo-buttons">Send</button>
</div>
<div id="overview-page"><br>
<div class="messages">
<ul id="inbox"></ul>
<ul id="outbox"></ul>
</div>
<div id="input-footer">
<button id="new-photo">Photo</button>
<button id="new-message">Message</button>
</div>
</div>
<script src="/web/main.js"></script>
<script src="/web/emoji.js"></script>
<script src="/web/photostuff.js"></script>
</body>
</html>

View File

@ -1,325 +0,0 @@
localStorage.clear();
//These need to be obtained from the node
var ownId, ownColor, ownAlias;
/*
* INIT
*/
document.addEventListener('DOMContentLoaded', function(){
function update(){
if ( !ownId ){
getOwnId();
}
if ( !ownAlias){
getOwnAlias();
}
checkInbox();
// also check for outbox items on interval,
// necessary in case connection is lost and messages are not yet sent
checkOutbox();
}
document.getElementById('message-form').onsubmit = onSubmitMessage;
//update everything to initialize
updateInboxView();
update();
//check for new messages every 7 seconds
window.setInterval( update, 7000 );
initState();
initPhotoStuff(); //all photostuff is found in photostuff.js
});
/*
* STATE CHANGES
*/
function initState(){
showOverview();
document.getElementById('new-photo').onclick = showNewPhoto;
document.getElementById('new-message').onclick = showNewMessage;
document.getElementById('message-back').onclick = showOverview;
document.getElementById('photo-back').onclick = showOverview;
}
function showNewPhoto(){
document.getElementById('photo-page').style.display = "block";
document.getElementById('overview-page').style.display = "none";
document.getElementById('message-page').style.display = "none";
document.getElementById('fileInput').click();
}
function showNewMessage(){
document.getElementById('photo-page').style.display = "none";
document.getElementById('overview-page').style.display = "none";
document.getElementById('message-page').style.display = "block";
}
function showOverview(){
document.getElementById('photo-page').style.display = "none";
document.getElementById('overview-page').style.display = "block";
document.getElementById('message-page').style.display = "none";
}
/*
* OUTBOX STUFF
*/
function onSubmitMessage(){
var msg = document.getElementById('message').value.replace(/\r?\n/g, "<br />");
if ( !msg || msg === "" ) { /* prevent sending of empty messages */
return false;
}
var namm = document.getElementById('name').value;
if ( !namm || namm === "" || namm.length > 20) { /* prevent too long usernames */
namm = "anonymous";
}
addOutboxItem( namm, "<p class='text-message'>"+msg+"</p>" );
return false;
}
function addOutboxItem( namm, message ){
var outStr = localStorage.getItem( 'outbox' ) || '';
var newMsgs = {};
var ddata = new Date().getTime();
var alias = ownAlias;
var contento = {
"time" : ddata,
"message" : message,
"name" : namm,
"node" : "local",
"alias": alias,
"hops" : "0"
};
newMsgs.message = contento;
localStorage.setItem( 'outbox', JSON.stringify(newMsgs) );
checkOutbox();
document.getElementById('message').value = '';
showOverview();
}
function checkOutbox() {
var outStr = localStorage.getItem( 'outbox' );
if ( ! outStr ) {
return;
}
var lines = outStr.split( /\n/ );
for ( var i in lines ) {
if ( lines[i].length === 0 ) {
continue;
}
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
delete obj.message.time;
var msg = JSON.stringify(obj.message);
sendMessage( ts, msg );
}
}
function sendMessage( timestamp, message ) {
var xhr = new XMLHttpRequest();
var data = 'time=' + encodeURIComponent( timestamp ) +
'&message=' + encodeURIComponent( message );
xhr.onreadystatechange = function(){
if ( xhr.readyState == 4){
if ( xhr.status == 200 ) {
removeOutboxItem( timestamp );
}
}
};
xhr.open('POST', 'send', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2005 00:00:00 GMT');
xhr.send(data);
}
function removeOutboxItem( timestamp ) {
var outStr = localStorage.getItem( 'outbox' ) || '';
var lines = outStr.split( /\n/ );
for ( var i in lines ) {
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
if ( ts === timestamp ) {
lines.splice( i, 1 );
break;
}
}
var newOutStr = lines.join('\n');
localStorage.setItem('outbox', newOutStr);
}
/*
* INBOX STUFF
*/
function updateInboxView() {
var localStorageArray = [];
var contentString = '';
if (localStorage.length>0) {
for (i=0;i<localStorage.length;i++){
element=localStorage.getItem(localStorage.key(i));
if ( localStorage.key(i).length < 10 || element === 'outbox' ) {
continue;
}
try {
elementj = JSON.parse(element);
} catch (e) {
continue;
}
localStorageArray[i] = {
time:localStorage.key(i),
user:elementj.name,
message:elementj.message,
node:elementj.node,
hops:elementj.hops,
alias:elementj.alias
};
}
}
orderStorage = localStorageArray.sort(function(a,b) { return b.time - a.time; } );
for(var i in orderStorage) {
if ( i.length === 0 || i === 'outbox' ) {
continue;
}
var datereadable = getReadableDate( new Date(parseInt(orderStorage[i].time)) );
var color = getNodeColor( orderStorage[i].node );
contentString += '<li><div class="message-block" style="background-color:'+color+'">'+
'<div class="date-sender">On ' + datereadable +
' <b>' + parseEmoticons(orderStorage[i].user) +'</b> wrote:</div>' +
'<div class="message-text">' + parseEmoticons( orderStorage[i].message ) + '</div>' + //parseEmoticons is found in emoji.js
' <div class="nodehops"><div class="node '+orderStorage[i].node+'">from '+orderStorage[i].alias + '</div>' +
' <div class="hops '+orderStorage[i].hops+'">via '+orderStorage[i].hops+' nodes</div></div></div></li>';
}
document.getElementById( 'inbox' ).innerHTML = contentString;
}
function getReadableDate( date ) {
var day = date.getDate();
if (day < 10) day = '0' + day;
var month = date.getMonth()+1;
if (month < 10) month = '0' + month;
var year = date.getFullYear();
var hrs = date.getHours();
if (hrs < 10) hrs = '0' + hrs;
var min = date.getMinutes();
if (min < 10) min = '0' + min;
return day + '/' + month + '/' + year + ' ' + hrs + ':' + min;
}
function getNodeColor( nodeId ) {
if( nodeId === 'local' ) {
return ownColor || '#fff';
}
return colorLuminance(nodeId.substr(0,6), 0.5);
}
function colorLuminance(hex, lum) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
}
lum = lum || 0;
// convert to decimal and change luminosity
var rgb = "#", c, i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i*2,2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00"+c).substr(c.length);
}
return rgb;
}
function onMessageDownload( msg, filename ) {
if ( localStorage.getItem( filename ) === null ) {
localStorage.setItem( filename, msg );
}
updateInboxView();
}
function onIndex( index ) {
var lines = index.split( /\n/ );
var k,i,l,f;
for( k in localStorage){
l = 1;
for ( i in lines ) {
f = lines[i];
if (f == k){ l = 0; }
}
if (l == 1){
localStorage.removeItem(k);
}
}
updateInboxView();
for ( i in lines ) {
var fname = lines[i];
if ( localStorage.getItem( fname ) === null ) {
//localStorage.setItem( ts, lines[i].substr(lines[i].indexOf(' ')) );
downloadMessage( fname );
}
}
}
function downloadMessage(filename) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
onMessageDownload( xhr.responseText, filename );
}
};
xhr.open( "GET", 'msg/'+filename, true);
xhr.send();
}
function checkInbox() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
onIndex( xhr.responseText );
}
};
xhr.open( "GET", 'index', true);
xhr.send();
}
function getOwnId() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
ownId = xhr.responseText;
ownColor = getNodeColor( ownId );
}
};
xhr.open( "GET", 'id', true);
xhr.send();
}
function getOwnAlias() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
ownAlias = xhr.responseText;
}
};
xhr.open( "GET", 'alias', true);
xhr.send();
}

View File

@ -1,225 +0,0 @@
/*
* PHOTO STUFF
*/
var imgDim = 200;
var canvas1;
var context1;
var canvas2;
var context2;
var imgObj;
var fileReader;
var rotation = 0;
var sourceImage = document.getElementById('sourceImage'); // get reference to img
var fileInput = document.getElementById('fileInput'); // get reference to file select
var progressEl = document.getElementById('progress');
var totalEl = document.getElementById('total');
function initPhotoStuff(){
canvas1 = document.getElementById('canvas1');
context1 = canvas1.getContext('2d');
canvas2 = document.getElementById('canvas2');
context2 = canvas2.getContext('2d');
canvas3 = document.getElementById('canvas3');
context3 = canvas3.getContext('2d');
initCanvas(context1);
initCanvas(context2);
initCanvas(context3);
fileInput.onchange = onFileInputChange;
document.getElementById('rot-left').onclick = onRotateLeft;
document.getElementById('rot-right').onclick = onRotateRight;
document.getElementById('submit-photo').onclick = submitImage;
}
function submitImage(){
var image = new Image(); //create new image holder
var canvas = document.getElementById('canvas3'); // choose canvas element to convert
var dataURL = canvas.toDataURL(); // convert cabvas to data url we can handle
image.src = dataURL;
image.className = 'photo-message';
if ( !image.src || image.src === "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAACEUlEQVR4nO3TMQEAIAzAsPk3DQrWF45EQZ/OAVbzOgB+ZhAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBcAEODrNzcVUZ+gAAAABJRU5ErkJggg==" ) { /* prevent sending of empty messages (data url of blank, thus white, canvas)*/
return false;
}
var namm = document.getElementById('photo-name').value;
if( !namm || namm == "" ){
namm = "anonymous";
}
addOutboxItem( parseEmoticons(namm), image.outerHTML );
showOverview();
return false;
}
function initCanvas(context){
// Debug filling
context.fillStyle ="#dbbd7a";
context.fill();
context.beginPath();
context.rect(0,0, imgDim, imgDim);
context.fillStyle = 'white';
context.fill();
// context.lineWidth = 7;
// context.strokeStyle = 'black';
// context.stroke();
}
// create file reader
function onFileInputChange(){
var file = fileInput.files[0]; // get selected file (camera capture)
fileReader = new FileReader();
fileReader.onload = onFileReaderLoad; // add onload listener
fileReader.onprogress = onFileReaderProgress; // add progress listener
fileReader.readAsDataURL( file ); // get captured image as data URI
}
function onFileReaderProgress(e) {
if (e.lengthComputable) {
var progress = parseInt( ((e.loaded / e.total) * 100), 10 );
console.log(progress);
progressEl.innerHTML = e.loaded;
totalEl.innerHTML = e.total;
}
}
function onFileReaderLoad() {
imgObj = new Image();
imgObj.src = fileReader.result;
imgObj.onload = onImageLoad;
//for debugging: show image on screen, will be squashed
sourceImage.src = fileReader.result;
}
function onImageLoad() {
var w,h;
var xOffset = 0;
var yOffset = 0;
if ( imgObj.width > imgObj.height ) {
h = imgDim;
w = imgDim * imgObj.width / imgObj.height;
xOffset = (imgDim - w) / 2;
} else {
w = imgDim;
h = imgDim * imgObj.height / imgObj.width;
yOffset = (imgDim - h) / 2;
}
context1.drawImage(imgObj, xOffset, yOffset, w, h);
var imageData = context1.getImageData( 0, 0, canvas1.width, canvas1.height);
context2.putImageData( monochrome(imageData,128,"atkinson"), 0, 0);
drawRotated();
}
function onRotateRight(){
rotation += 90;
drawRotated();
}
function onRotateLeft(){
rotation -= 90;
drawRotated();
}
function drawRotated(){
context3.clearRect(0,0,canvas3.width,canvas3.height);
context3.save();
context3.translate(canvas3.width/2,canvas3.height/2);
context3.rotate(rotation * Math.PI / 180);
context3.translate(-canvas3.width/2,-canvas3.height/2);
context3.drawImage( canvas2, 0, 0 );
context3.restore();
}
var bayerThresholdMap = [
[ 15, 135, 45, 165 ],
[ 195, 75, 225, 105 ],
[ 60, 180, 30, 150 ],
[ 240, 120, 210, 90 ]
];
var lumR = [];
var lumG = [];
var lumB = [];
for (var i=0; i<256; i++) {
lumR[i] = i*0.299;
lumG[i] = i*0.587;
lumB[i] = i*0.114;
}
function monochrome(imageData, threshold, type){
var imageDataLength = imageData.data.length;
// Greyscale luminance (sets r pixels to luminance of rgb)
for (var i = 0; i <= imageDataLength; i += 4) {
imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i+1]] + lumB[imageData.data[i+2]]);
}
var w = imageData.width;
var newPixel, err;
for (var currentPixel = 0; currentPixel <= imageDataLength; currentPixel+=4) {
if (type === "none") {
// No dithering
imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255;
} else if (type === "bayer") {
// 4x4 Bayer ordered dithering algorithm
var x = currentPixel/4 % w;
var y = Math.floor(currentPixel/4 / w);
var map = Math.floor( (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2 );
imageData.data[currentPixel] = (map < threshold) ? 0 : 255;
} else if (type === "floydsteinberg") {
// FloydSteinberg dithering algorithm
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
imageData.data[currentPixel] = newPixel;
imageData.data[currentPixel + 4 ] += err*7;
imageData.data[currentPixel + 4*w - 4 ] += err*3;
imageData.data[currentPixel + 4*w ] += err*5;
imageData.data[currentPixel + 4*w + 4 ] += err*1;
} else {
// Bill Atkinson's dithering algorithm
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
imageData.data[currentPixel] = newPixel;
imageData.data[currentPixel + 4 ] += err;
imageData.data[currentPixel + 8 ] += err;
imageData.data[currentPixel + 4*w - 4 ] += err;
imageData.data[currentPixel + 4*w ] += err;
imageData.data[currentPixel + 4*w + 4 ] += err;
imageData.data[currentPixel + 8*w ] += err;
}
// Set g and b pixels equal to r
imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
}
// Alpha: make white pixels transparent!
var newColor = {r:0,g:0,b:0, a:0};
for ( i = 0, n = imageData.data.length; i <n; i += 4) {
var r = imageData.data[i],
g = imageData.data[i+1],
b = imageData.data[i+2];
// If its white, or close to white then change it
if(r >=200 && g >= 200 && b >= 200){
// Change the white to whatever.
imageData.data[i] = newColor.r;
imageData.data[i+1] = newColor.g;
imageData.data[i+2] = newColor.b;
imageData.data[i+3] = newColor.a;
}
}
return imageData;
}

View File

@ -1,523 +0,0 @@
body, html{
margin: 0;
padding: 0;
}
body{
font-size:20px;
text-shadow: 1px 1px #ddd;
font-family: times, 'times new roman', helvetica,serif;
line-height:1.5em;
color:#444;
background:#fff;
padding:20px;
}
/* resize images on mobile */
@media all and (max-width: 768px) {
.photo-message{ /* rounded corner imgs...*/
width:100%;
height:100%;
overflow:hidden;
line-height: 0;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
}
img.photo-message{
width:100%;
line-height: 0;
vertical-align:middle; /* otherwise produces bottom border the size of the body 'line-height' */
/* disable anti aliasing */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
.nodehops{
position: absolute;
float: right;
right:20px;
text-align: right;
padding: 0px;
margin-top:-50px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
}
/* resize images on desktop */
@media all and (min-width: 768px) {
img.photo-message{
width:50%;
display: block;
margin-left: auto;
margin-right: auto;
/* disable anti aliasing */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
img.photo-message{
/* disable anti aliasing */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
.photo-message{ /* rounded corner imgs on desktop...*/
padding:30px;
width:100%;
height:100%;
overflow:hidden;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
}
.nodehops{
position: absolute;
float: right;
right:20px;
text-align: right;
padding: 4px;
margin-top:-60px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
}
img.emo {
}
.date-sender .emo {
width:22px;
height:22px;
}
p.text-message{
padding: 30px;
padding-top: 50px;
}
#input-footer{
position: fixed;
top:0px;
width:100%;
margin:10px;
}
#new-photo{
display:inline;
float:left;
width:30%;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
#new-message{
margin-right: 60px;
display:inline;
width:30%;
float:right;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.back-button{
display:inline;
float:left;
width:100%;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.rot-left{
display:inline;
float:left;
width:50%;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.rot-right{
display:inline;
width:50%;
float:right;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.photo-buttons {
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
width:100%;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
/* hide calculation canvases */
#sourceImage{
display: none;
}
#canvas1{
display: none;
}
#canvas2{
display: none;
}
#progress{
display: none;
}
#total{
display: none;
}
#message-form textarea{
-webkit-appearance: none;
-moz-appearance: none;
display: block;
outline:none;
width: 98%;
margin: 0 auto;
line-height: 40px;
border:none;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
font-family: inherit;
font-size: 20px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
#photo-name{ /* text field */
-webkit-appearance: none;
-moz-appearance: none;
display: block;
outline:none;
width: 98%;
margin: 0 auto;
line-height: 40px;
border:none;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
font-family: inherit;
font-size: 20px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
#message-form .send-button {
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
width:100%;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
select{
width:100%;
display: block;
margin-left: auto;
margin-right: auto;
margin: 1.5em 0;
font-family: inherit;
font-size: inherit;
font-size: 20px;
}
h2{
margin: 0 0 20px;
font-size: 40px;
text-shadow: 0px 0px 23px #bbb;
}
hr{
height:10px;
border: 10;
width:87%;
background-image: url(css/bg1.gif);
}
.messages ul{
padding:0px;
margin: 0;
}
.messages li{
list-style: none;
margin: 0;
}
.messages .message-block {
margin-top:30px;
word-wrap:break-word;
overflow: visible;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
.photoBox {
width: 100%;
margin: 0 auto;
line-height: 40px;
display: none;
position: absolute;
background-color:#fff;
z-index:1002;
overflow: visible;
background:#fff;
}
.node, .hops {
font-size:10px;
display: inline-block;
word-wrap:break-word;
overflow: visible;
}
.message-text {
font-size: 25px;
}
.date-sender {
position: absolute;
font-size: 15px;
padding: 4px;
word-wrap:break-word;
overflow: visible;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
#outbox{
display:none;
}
#header{
width:100%;
text-align: center;
}

View File

@ -355,7 +355,7 @@ updateOutboxView();
window.setInterval( function(){ window.setInterval( function(){
checkInbox(); checkInbox();
checkOutbox(); checkOutbox();
}, 7000 ); }, 3000 );
</script> </script>