Compare commits
No commits in common. "master" and "logging" have entirely different histories.
14
README.md
@ -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.
|
||||
@ -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)
|
||||
|
||||
*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
|
||||
|
||||
To use your router for Meshenger you're going to need to run the whole filesystem from a USB-Drive.
|
||||
|
@ -1,46 +1,3 @@
|
||||
This is a very primitive script to automate the openwrt setup.
|
||||
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!
|
||||
|
@ -17,7 +17,7 @@ mount -t ext4 /dev/sda1 /mnt/sda1
|
||||
|
||||
echo 'Cleaning USB drive'
|
||||
|
||||
rm -r /mnt/sda1/*
|
||||
rm -r /mnt/sda1/
|
||||
|
||||
sleep 4
|
||||
|
||||
@ -25,7 +25,7 @@ echo 'Copying filesystem to USB drive'
|
||||
mkdir -p /tmp/cproot
|
||||
mount --bind / /tmp/cproot
|
||||
sleep 1
|
||||
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1/ -xf -
|
||||
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1 -xf -
|
||||
sleep 1
|
||||
umount /tmp/cproot
|
||||
|
||||
|
@ -32,7 +32,7 @@ sed -i -e "s/option 'interfaces' 'mesh'/option 'interfaces' 'adhoc0'/g" /etc/con
|
||||
|
||||
opkg install python git
|
||||
sleep 1
|
||||
git clone git://github.com/rscmbbng/meshenger /root/meshenger
|
||||
git clone ://github.com/rscmbbng/meshenger /root/meshenger
|
||||
|
||||
mv uhttpd /etc/config/uhttpd
|
||||
|
||||
|
@ -7,7 +7,7 @@ config wifi-iface 'wmesh'
|
||||
option bssid '66:66:66:66:66:66'
|
||||
|
||||
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 'network' 'hotspot'
|
||||
option 'mode' 'ap'
|
||||
|
99
main.py
@ -2,39 +2,31 @@
|
||||
|
||||
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')
|
||||
logger = logging.getLogger('meshenger'+'.main')
|
||||
|
||||
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_old = {}
|
||||
devices = {} #the dictionary of all the nodes this this node has seen
|
||||
serve_port = "13338"
|
||||
announce_port = 13337
|
||||
#own_ip = "0.0.0.0"
|
||||
msg_dir = os.path.relpath('msg/')
|
||||
exitapp = False #to kill all threads on
|
||||
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):
|
||||
|
||||
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.chdir(os.path.dirname(__file__)) # change present working directory to the one where this file is
|
||||
|
||||
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):
|
||||
os.mkdir(self.msg_dir)
|
||||
logger.info('Making message directory')
|
||||
|
||||
self.init_index()
|
||||
self.make_alias()
|
||||
|
||||
try:
|
||||
d = threading.Thread(target=self.discover)
|
||||
d.daemon = True
|
||||
@ -52,6 +44,12 @@ class Meshenger:
|
||||
c.daemon = True
|
||||
c.start()
|
||||
|
||||
b = threading.Thread(target=self.build_index)
|
||||
b.daemon = True
|
||||
b.start()
|
||||
|
||||
#os.system("python meshenger_clientserve.py")
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
logger.info('exiting discovery thread')
|
||||
d.join()
|
||||
@ -60,10 +58,6 @@ class Meshenger:
|
||||
n.join()
|
||||
c.join()
|
||||
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:
|
||||
logger.debug('Entering main loop')
|
||||
@ -91,27 +85,15 @@ class Meshenger:
|
||||
self.get_index(device, nodepath)
|
||||
logger.info('downloading messages')
|
||||
self.get_messages(device, nodepath, nodehash)
|
||||
self.build_index()
|
||||
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
|
||||
|
||||
|
||||
def node_timestamp(self, ip):
|
||||
nodepath = os.path.abspath(os.path.join('nodes', self.hasj(ip)))
|
||||
updatepath = os.path.join(nodepath, 'lastupdate')
|
||||
with open(updatepath, 'wb') as lastupdate:
|
||||
lastupdate.write(self.devices[ip][0])
|
||||
lastupdate.write(self.devices[ip])
|
||||
#return updatepath
|
||||
|
||||
|
||||
@ -122,20 +104,17 @@ Announce the node's existance to other nodes
|
||||
"""
|
||||
logger.info('Announcing')
|
||||
while not self.exitapp:
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
|
||||
#sock.close()
|
||||
sock.close()
|
||||
time.sleep(5)
|
||||
except:
|
||||
logger.warning('Failed to announce myself!')
|
||||
|
||||
def discover(self):
|
||||
"""
|
||||
Discover other devices by listening to the Meshenger announce port
|
||||
"""
|
||||
|
||||
logger.info('Discovering')
|
||||
print 'Discovering'
|
||||
bufferSize = 1024 # whatever you need?
|
||||
|
||||
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:
|
||||
result = select.select([s],[],[])[0][0].recvfrom(bufferSize)
|
||||
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))
|
||||
announce_time = str(int(time.time()))
|
||||
|
||||
if not os.path.exists(node_path) and ip != self.own_ip:
|
||||
#loop for first time
|
||||
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
|
||||
logger.info('New node %s', ip)
|
||||
|
||||
elif os.path.exists(node_path) and ip != self.own_ip:
|
||||
logger.info('Known node %s', ip)
|
||||
self.devices[ip] = result[0], announce_time
|
||||
self.devices[ip] = result[0]
|
||||
|
||||
|
||||
time.sleep(1)
|
||||
@ -175,33 +154,27 @@ Initialize the clientserver
|
||||
"""
|
||||
logger.info('Serving to client')
|
||||
import meshenger_clientserve
|
||||
# set a reference to this object
|
||||
meshenger_clientserve.meshenger = self
|
||||
meshenger_clientserve.main()
|
||||
# meshenger_clientserve.build_index_callback = self.build_index
|
||||
|
||||
def init_index(self):
|
||||
"""
|
||||
Initialize the index. Read from disk or create new.
|
||||
"""
|
||||
logger.info('Checking own index for the first time\n')
|
||||
|
||||
if not os.path.exists('index'):
|
||||
with open('index','wb') as index:
|
||||
index.write('')
|
||||
self.previous_index = []
|
||||
else:
|
||||
self.previous_index = open('index').readlines()
|
||||
self.build_index()
|
||||
|
||||
def build_index(self):
|
||||
"""
|
||||
Make an index file of all the messages present on the node.
|
||||
Save the time of the last update.
|
||||
"""
|
||||
logger.debug('build_index')
|
||||
|
||||
logger.info('Building own index for the first time\n')
|
||||
|
||||
if not os.path.exists('index'):
|
||||
with open('index','wb') as index:
|
||||
index.write('')
|
||||
previous_index = []
|
||||
else:
|
||||
previous_index = open('index').readlines()
|
||||
|
||||
while not self.exitapp:
|
||||
current_index = os.listdir(self.msg_dir)
|
||||
if current_index != self.previous_index:
|
||||
if current_index != previous_index:
|
||||
with open('index', 'wb') as index:
|
||||
for message in os.listdir(self.msg_dir):
|
||||
index.write(message)
|
||||
@ -213,21 +186,8 @@ Initialize the index. Read from disk or create new.
|
||||
with open('index_last_update', 'wb') as indexupdate:
|
||||
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):
|
||||
"""
|
||||
@ -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
|
||||
"""
|
||||
#TODO possibly check if it's an empty file
|
||||
if not os.path.isfile('interfaceip6adress'):
|
||||
os.system('ifconfig -a adhoc0 | grep inet6 > /root/meshenger/interfaceip6adress')
|
||||
with open('/root/meshenger/interfaceip6adress', 'r') as a:
|
||||
|
@ -13,34 +13,27 @@ import logging, logging.config
|
||||
logging.config.fileConfig('pylog.conf')
|
||||
logger = logging.getLogger('meshenger'+'.clientserve')
|
||||
|
||||
|
||||
class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
|
||||
messageDir = "msg"
|
||||
|
||||
def do_GET(self):
|
||||
"""
|
||||
Serve index and messages
|
||||
"""
|
||||
def do_GET(self):
|
||||
|
||||
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)
|
||||
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':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text-html')
|
||||
@ -48,33 +41,21 @@ Serve index and messages
|
||||
f = os.path.relpath('log/meshenger.log')
|
||||
with open( f, 'r') as the_file:
|
||||
self.wfile.write(the_file.read())
|
||||
elif self.path.startswith('/web'):
|
||||
|
||||
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':
|
||||
else:
|
||||
self.send_response(200) #serve the webapp on every url requested
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
f = os.path.relpath( 'webapp.html')
|
||||
f = os.path.join( "webapp.html")
|
||||
with open( f, 'r') as the_file:
|
||||
self.wfile.write(the_file.read())
|
||||
|
||||
else:
|
||||
self.path = '/' + os.path.join('web', 'index.html')
|
||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
|
||||
def do_POST(self):
|
||||
"""
|
||||
Allow clients to post messages
|
||||
"""
|
||||
def do_POST(self):
|
||||
if self.path == '/send':
|
||||
logger.info('incoming message from client!')
|
||||
|
||||
length = int(self.headers['Content-Length'])
|
||||
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.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):
|
||||
"""
|
||||
Write message to disk
|
||||
"""
|
||||
|
||||
f = os.path.join( self.messageDir, time)
|
||||
if os.path.isfile( f ):
|
||||
return
|
||||
@ -106,7 +77,6 @@ Write message to disk
|
||||
|
||||
class ClientServe():
|
||||
def __init__(self, port):
|
||||
logger.info('ClientServe.__init__')
|
||||
server = HTTPServer( ('', port), ClientServeHandler)
|
||||
server.serve_forever()
|
||||
|
||||
|
@ -28,20 +28,16 @@ class NodeServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
|
||||
messageDir = "/msg"
|
||||
|
||||
def do_GET(self):
|
||||
"""
|
||||
Serve index and messages
|
||||
"""
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.path = "/index"
|
||||
|
||||
if self.path == '/index' or self.path.startswith( self.messageDir ):
|
||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
if self.path == '/alias' or self.path.startswith( self.messageDir ):
|
||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
@ -53,7 +49,6 @@ class HTTPServerV6(BaseHTTPServer.HTTPServer):
|
||||
|
||||
class NodeServe():
|
||||
def __init__(self, port):
|
||||
logger.info('NodeServe.__init__')
|
||||
server = HTTPServerV6(('::', port), NodeServeHandler)
|
||||
server.serve_forever()
|
||||
|
||||
|
45
web/emoji.js
@ -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;
|
||||
}
|
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.2 KiB |
@ -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>
|
||||
|
325
web/main.js
@ -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();
|
||||
}
|
||||
|
@ -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") {
|
||||
// Floyd–Steinberg 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;
|
||||
}
|
523
web/style.css
@ -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;
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ updateOutboxView();
|
||||
window.setInterval( function(){
|
||||
checkInbox();
|
||||
checkOutbox();
|
||||
}, 7000 );
|
||||
}, 3000 );
|
||||
|
||||
</script>
|
||||
|
||||
|