├── .gitignore
├── .gitmodules
├── Procfile
├── bin
└── pre_compile
├── config.py
├── license
├── main.py
├── org
├── __init__.py
└── collabdraw
│ ├── __init__.py
│ ├── dbclient
│ ├── __init__.py
│ ├── dbclientfactory.py
│ ├── dbclienttypes.py
│ ├── dbinterface.py
│ └── redisdbclient.py
│ ├── handler
│ ├── __init__.py
│ ├── loginhandler.py
│ ├── logouthandler.py
│ ├── registerhandler.py
│ ├── uploadhandler.py
│ └── websockethandler.py
│ ├── pubsub
│ ├── __init__.py
│ ├── pubsubclientfactory.py
│ ├── pubsubclienttypes.py
│ ├── pubsubinterface.py
│ └── redispubsubclient.py
│ └── tools
│ ├── __init__.py
│ ├── tools.py
│ ├── uploadprocessor.py
│ └── videomaker.py
├── readme.md
├── requirements.txt
├── resource
├── css
│ └── upload.css
├── html
│ ├── index.html
│ ├── login.html
│ ├── register.html
│ └── upload.html
└── js
│ ├── App.js
│ ├── Connection.js
│ ├── Login.js
│ ├── MessageEvent.js
│ ├── Register.js
│ ├── Svg.js
│ ├── Upload.js
│ ├── jsxcompressor.js
│ ├── package.js
│ ├── raphael-min.js
│ └── upload.js
├── run
├── run_tests.sh
├── runtime.txt
├── set_heroku_path.sh
└── test
├── __init__.py
├── files
└── sample.pdf
├── uploadprocessor_integ_test.py
└── videomaker_integ_test.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | touch
3 | resource/touch
4 | venv
5 | *.pyc
6 | *~
7 | .idea
8 | *.png
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "resource/enyo"]
2 | path = resource/enyo
3 | url = https://github.com/enyojs/enyo.git
4 | [submodule "resource/lib/layout"]
5 | path = resource/lib/layout
6 | url = https://github.com/enyojs/layout.git
7 | [submodule "resource/lib/onyx"]
8 | path = resource/lib/onyx
9 | url = https://github.com/enyojs/onyx.git
10 | [submodule "collabdraw-heroku-bin"]
11 | path = collabdraw-heroku-bin
12 | url = https://github.com/anandtrex/collabdraw-heroku-bin.git
13 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: python main.py
2 |
--------------------------------------------------------------------------------
/bin/pre_compile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | INSTALL_DIR=/app
4 | ROOT_DIR=/app
5 |
6 | function install_binary {
7 | BINARY=$1
8 | VENDOR=$2
9 | INSTALL_PATH=$INSTALL_DIR/$VENDOR
10 | BINARY_PATH=$ROOT_DIR/$BINARY
11 | echo "Installing $BINARY to $INSTALL_PATH"
12 | if [ ! -d "$INSTALL_PATH" ]; then
13 | mkdir -p $INSTALL_PATH
14 | echo "Created $INSTALL_PATH"
15 | echo "Downloading from $BINARY"
16 | tar -xz -C $INSTALL_PATH -f $BINARY_PATH
17 | fi
18 | }
19 |
20 |
21 | FFMPEG_BINARY="collabdraw-heroku-bin/ffmpeg.tgz"
22 | FFMPEG_VENDOR="vendor/ffmpeg"
23 |
24 | install_binary $FFMPEG_BINARY $FFMPEG_VENDOR
25 |
26 | IMAGEMAGICK_BINARY="collabdraw-heroku-bin/imagemagick.tgz"
27 | IMAGEMAGICK_VENDOR="vendor/imagemagick"
28 |
29 | install_binary $IMAGEMAGICK_BINARY $IMAGEMAGICK_VENDOR
30 |
31 | FONTCONFIG_BINARY="collabdraw-heroku-bin/fontconfig.tgz"
32 | FONTCONFIG_VENDOR="vendor/fontconfig-2.10"
33 |
34 | install_binary $FONTCONFIG_BINARY $FONTCONFIG_VENDOR
35 |
36 | POPPLER_BINARY="collabdraw-heroku-bin/poppler.tgz"
37 | POPPLER_VENDOR="vendor/poppler"
38 |
39 | install_binary $POPPLER_BINARY $POPPLER_VENDOR
40 |
41 | CAIRO_BINARY="collabdraw-heroku-bin/cairo.tgz"
42 | CAIRO_VENDOR="vendor/cairo"
43 |
44 | install_binary $CAIRO_BINARY $CAIRO_VENDOR
45 |
46 | PIXMAN_BINARY="collabdraw-heroku-bin/pixman.tgz"
47 | PIXMAN_VENDOR="vendor/pixman"
48 |
49 | install_binary $PIXMAN_BINARY $PIXMAN_VENDOR
50 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | # App's host and port
4 | APP_IP_ADDRESS = "192.168.1.134" # Put your websocket endpoint here
5 | APP_PORT = os.environ.get('PORT', 5000)
6 |
7 | # Port in which websocket client should listen
8 | # Usually same as APP_PORT unless some other
9 | # port forwarding is set up (for ex. if you're using heroku)
10 | PUBLIC_LISTEN_PORT = APP_PORT
11 |
12 | PUBSUB_CLIENT_TYPE = 'redis' # only redis supported now
13 | DB_CLIENT_TYPE = 'redis' # only redis supported now
14 |
15 | REDIS_URL = os.environ.get('REDISCLOUD_URL', 'redis://localhost:6379')
16 |
17 | # Full path of "collabdraw" directory
18 | ROOT_DIR = "/".join(os.path.realpath(__file__).split('/')[:-1])
19 | RESOURCE_DIR = os.path.join(ROOT_DIR, 'resource')
20 | HTML_ROOT = os.path.join(RESOURCE_DIR, 'html')
21 |
22 | # Hash salt for storing password in database
23 | HASH_SALT = "bollacboard"
24 |
25 | # Enable SSL/TLS
26 | ENABLE_SSL = False
27 | SERVER_CERT = os.path.join(os.getcwd(), "server.crt")
28 | SERVER_KEY = os.path.join(os.getcwd(), "server.key")
29 |
30 | # Demo mode disables login requirement
31 | DEMO_MODE = True
32 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | CollabDraw: Whiteboard application in HTML5
2 | Copyright (C) 2013 Anand S
3 |
4 | Licensed under the GNU GPLv2 available at http://www.gnu.org/licenses/gpl-2.0.html#SEC3
5 |
6 | This program is free software; you can redistribute it and/or
7 | modify it under the terms of the GNU General Public License
8 | as published by the Free Software Foundation; either version 2
9 | of the License, or (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program; if not, write to the Free Software
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import uuid
3 | import config
4 | from os.path import join
5 |
6 | import tornado.httpserver
7 | import tornado.ioloop
8 | import tornado.web
9 | import tornado.template as template
10 |
11 | from org.collabdraw.handler.websockethandler import RealtimeHandler
12 | from org.collabdraw.handler.uploadhandler import UploadHandler
13 | from org.collabdraw.handler.loginhandler import LoginHandler
14 | from org.collabdraw.handler.logouthandler import LogoutHandler
15 | from org.collabdraw.handler.registerhandler import RegisterHandler
16 |
17 | logger = logging.getLogger('websocket')
18 | logger.setLevel(logging.INFO)
19 |
20 | ch = logging.StreamHandler()
21 | ch.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
22 | ch.setLevel(logging.INFO)
23 | logger.addHandler(ch)
24 |
25 |
26 | class IndexHandler(tornado.web.RequestHandler):
27 | def get_current_user(self):
28 | if not config.DEMO_MODE:
29 | return self.get_secure_cookie("loginId")
30 | else:
31 | return True
32 |
33 | @tornado.web.authenticated
34 | def get(self):
35 | loader = template.Loader(config.ROOT_DIR)
36 | return_str = loader.load(join(config.HTML_ROOT, "index.html")).generate(app_ip_address=config.APP_IP_ADDRESS,
37 | app_port=config.PUBLIC_LISTEN_PORT)
38 | self.finish(return_str)
39 |
40 |
41 | class Application(tornado.web.Application):
42 | def __init__(self):
43 | handlers = [
44 | (r'/realtime/', RealtimeHandler),
45 | (r'/resource/(.*)', tornado.web.StaticFileHandler,
46 | dict(path=config.RESOURCE_DIR)),
47 | (r'/upload', UploadHandler),
48 | (r'/login.html', LoginHandler),
49 | (r'/logout.html', LogoutHandler),
50 | (r'/register.html', RegisterHandler),
51 | (r'/index.html', IndexHandler),
52 | (r'/', IndexHandler),
53 | (r'/(.*)', tornado.web.StaticFileHandler,
54 | dict(path=config.ROOT_DIR)),
55 | ]
56 |
57 | settings = dict(
58 | auto_reload=True,
59 | gzip=True,
60 | login_url="login.html",
61 | cookie_secret=str(uuid.uuid4()),
62 | )
63 |
64 | tornado.web.Application.__init__(self, handlers, **settings)
65 |
66 |
67 | if __name__ == "__main__":
68 | if not config.ENABLE_SSL:
69 | http_server = tornado.httpserver.HTTPServer(Application())
70 | else:
71 | http_server = tornado.httpserver.HTTPServer(Application(), ssl_options={
72 | "certfile": config.SERVER_CERT,
73 | "keyfile": config.SERVER_KEY,
74 | })
75 | logger.info("Listening on port %s" % config.APP_PORT)
76 | http_server.listen(config.APP_PORT)
77 | tornado.ioloop.IOLoop.instance().start()
78 |
--------------------------------------------------------------------------------
/org/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anandtrex/collabdraw/4de4cc14e344b2bf18d7c89846bdcecb92ee5125/org/__init__.py
--------------------------------------------------------------------------------
/org/collabdraw/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
--------------------------------------------------------------------------------
/org/collabdraw/dbclient/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
--------------------------------------------------------------------------------
/org/collabdraw/dbclient/dbclientfactory.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import logging
4 |
5 | from .redisdbclient import RedisDbClient
6 | from .dbclienttypes import DbClientTypes
7 | from .dbinterface import DbInterface
8 |
9 |
10 | class DbClientFactory:
11 | @staticmethod
12 | def getDbClient(db_client_type_str):
13 | """
14 | @param db_client_type_str:
15 | @rtype : DbInterface
16 | """
17 | logger = logging.getLogger('websocket')
18 | logger.info("Initializing with db client type %s" % db_client_type_str)
19 | if db_client_type_str == DbClientTypes.redis:
20 | return RedisDbClient()
21 | elif db_client_type_str == DbClientTypes.in_memory:
22 | pass
23 | else:
24 | raise RuntimeError("Unknown db client type %s" % db_client_type_str)
--------------------------------------------------------------------------------
/org/collabdraw/dbclient/dbclienttypes.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 |
4 | class DbClientTypes:
5 | redis = "redis"
6 | in_memory = "in-memory"
--------------------------------------------------------------------------------
/org/collabdraw/dbclient/dbinterface.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 |
4 | class DbInterface():
5 | def set(self, key, value):
6 | raise RuntimeError("DB Interface method %s not implemented" % "set")
7 |
8 | def get(self, key):
9 | raise RuntimeError("DB Interface method %s not implemented" % "get")
10 |
11 | def delete(self, key):
12 | raise RuntimeError("DB Interface method %s not implemented" % "delete")
--------------------------------------------------------------------------------
/org/collabdraw/dbclient/redisdbclient.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import logging
4 |
5 | import redis
6 |
7 | import config
8 | from .dbinterface import DbInterface
9 |
10 |
11 | class RedisDbClient(DbInterface):
12 | redis_client = redis.from_url(config.REDIS_URL)
13 |
14 | def __init__(self):
15 | self.logger = logging.getLogger('websocket')
16 |
17 | def set(self, key, value):
18 | self.redis_client.set(key, value)
19 |
20 | def get(self, key):
21 | value = self.redis_client.get(key)
22 | if value:
23 | return value.decode('utf-8').replace("'", '"')
24 |
25 | def delete(self, key):
26 | self.redis_client.delete(key)
27 |
--------------------------------------------------------------------------------
/org/collabdraw/handler/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
--------------------------------------------------------------------------------
/org/collabdraw/handler/loginhandler.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import config
4 |
5 | import tornado.web
6 |
7 | from ..dbclient.dbclientfactory import DbClientFactory
8 | from ..tools.tools import hash_password
9 |
10 |
11 | class LoginHandler(tornado.web.RequestHandler):
12 | def initialize(self):
13 | self.db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
14 | self.logger = logging.getLogger('websocket')
15 |
16 | def get(self):
17 | self.render(os.path.join(config.HTML_ROOT, "login.html"))
18 |
19 | def post(self):
20 | login_id = self.get_argument("loginId")
21 | login_password = self.get_argument("loginPassword")
22 | redis_key = "users:%s" % login_id
23 | db_password = self.db_client.get(redis_key)
24 | if db_password:
25 | db_password = db_password.decode('utf-8')
26 | if db_password != hash_password(login_password):
27 | self.logger.debug("db_password was %s but login_password was %s" % (db_password,
28 | login_password))
29 | self.finish('{"result": "failure"}')
30 | return
31 | self.logger.info("Logging in user %s", login_id)
32 | self.set_secure_cookie("loginId", login_id)
33 | self.finish('{"result": "success"}')
34 |
--------------------------------------------------------------------------------
/org/collabdraw/handler/logouthandler.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import tornado.web
4 |
5 |
6 | class LogoutHandler(tornado.web.RequestHandler):
7 | def initialize(self):
8 | self.logger = logging.getLogger('websocket')
9 |
10 | def get(self):
11 | self.redirect("./login.html")
12 |
13 | def post(self):
14 | self.logout()
15 |
16 | def logout(self):
17 | loginId = self.get_secure_cookie("loginId")
18 | self.logger.info("Logging out %s" % loginId)
19 | self.set_secure_cookie("loginId", "")
20 |
--------------------------------------------------------------------------------
/org/collabdraw/handler/registerhandler.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import config
4 |
5 | import tornado.web
6 |
7 | from ..dbclient.dbclientfactory import DbClientFactory
8 | from ..tools.tools import hash_password
9 |
10 |
11 | class RegisterHandler(tornado.web.RequestHandler):
12 | def initialize(self):
13 | self.logger = logging.getLogger('websocket')
14 | self.db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
15 |
16 | def get(self):
17 | self.render(os.path.join(config.HTML_ROOT, "register.html"))
18 |
19 | def post(self):
20 | login_id = self.get_argument("loginId")
21 | login_password = self.get_argument("loginPassword")
22 | redis_key = "users:%s" % login_id
23 | if self.db_client.get(redis_key):
24 | self.finish('{"result": "conflict"}')
25 | return
26 | self.db_client.set(redis_key, hash_password(login_password))
27 | self.logger.info("Logging in user %s", login_id)
28 | self.set_secure_cookie("loginId", login_id)
29 | self.finish('{"result": "success"}')
30 |
--------------------------------------------------------------------------------
/org/collabdraw/handler/uploadhandler.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import threading
3 | import config
4 |
5 | import os
6 | import tornado.web
7 | import tornado.template as template
8 |
9 | from ..dbclient.dbclientfactory import DbClientFactory
10 | from ..tools.uploadprocessor import process_uploaded_file
11 |
12 |
13 | class UploadHandler(tornado.web.RequestHandler):
14 | def initialize(self):
15 | self.logger = logging.getLogger('websocket')
16 | self.db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
17 |
18 | def get(self):
19 | self.room_name = self.get_argument('room', '')
20 | loader = template.Loader(config.ROOT_DIR)
21 | return_str = loader.load(os.path.join(config.HTML_ROOT, "upload.html")).generate(room=self.room_name)
22 | self.logger.info("Room name is %s" % self.room_name)
23 | self.finish(return_str)
24 |
25 | def post(self):
26 | return_str = "
%s. Will redirect back to the upload page in 5\
29 | seconds"
30 | self.room_name = self.get_argument('room', '')
31 | self.logger.info("Room name is %s" % self.room_name)
32 | if not self.room_name:
33 | self.logger.error("Unknown room name. Ignoring upload")
34 | response_str = "Room name not provided"
35 | self.finish(return_str % (self.room_name, response_str))
36 | return
37 | self.logger.debug("Room name is %s" % self.room_name)
38 | fileinfo = self.request.files['file'][0]
39 | fname = fileinfo['filename']
40 | fext = os.path.splitext(fname)[1]
41 | if fext.lower() != '.pdf':
42 | self.logger.error("Extension is not pdf. It is %s" % fext)
43 | response_str = "Only pdf files are allowed"
44 | self.finish(return_str % (self.room_name, response_str))
45 | return
46 | dir_path = os.path.join(config.ROOT_DIR, "files", self.room_name)
47 | os.makedirs(dir_path, exist_ok=True)
48 | file_path = os.path.join(dir_path, fname)
49 | fh = open(file_path, 'wb')
50 | fh.write(fileinfo['body'])
51 | fh.close()
52 | threading.Thread(target=process_uploaded_file, args=(dir_path, fname, self.room_name)).start()
53 | response_str = "Upload finished successfully"
54 | self.finish(return_str % (self.room_name, response_str))
55 |
56 |
--------------------------------------------------------------------------------
/org/collabdraw/handler/websockethandler.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import json
3 | from zlib import compress
4 | from urllib.parse import quote
5 | import config
6 |
7 | import os
8 | from base64 import b64encode
9 | import tornado.websocket
10 | import tornado.web
11 | from pystacia import read
12 |
13 | from ..dbclient.dbclientfactory import DbClientFactory
14 | from ..pubsub.pubsubclientfactory import PubSubClientFactory
15 | from ..tools.videomaker import make_video
16 |
17 |
18 | class RealtimeHandler(tornado.websocket.WebSocketHandler):
19 | room_name = ''
20 | paths = []
21 | db_client = None
22 | page_no = 1
23 | num_pages = 1
24 |
25 | # @Override
26 | def open(self):
27 | self.logger = logging.getLogger('websocket')
28 | self.logger.info("Open connection")
29 | self.db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
30 | self.pubsub_client = PubSubClientFactory.getPubSubClient(config.PUBSUB_CLIENT_TYPE)
31 | self.send_message(self.construct_message("ready"))
32 |
33 | # @Override
34 | def on_message(self, message):
35 | m = json.loads(message)
36 | event = m.get('event', '').strip()
37 | data = m.get('data', {})
38 |
39 | self.logger.debug("Processing event %s" % event)
40 | if not event:
41 | self.logger.error("No event specified")
42 | return
43 |
44 | if event == "init":
45 | self.logger.info("Initializing with room name %s" % self.room_name)
46 | room_name = data.get('room', '')
47 | if not room_name:
48 | self.logger.error("Room name not provided. Can't initialize")
49 | return
50 | page_no = data.get('page', '1')
51 |
52 | self.init(room_name, page_no)
53 |
54 | elif event == "draw-click":
55 | self.logger.debug("Received draw-click")
56 | single_path = data['singlePath']
57 | if not self.paths:
58 | self.logger.debug("None")
59 | self.paths = []
60 |
61 | self.paths.extend(single_path)
62 | self.broadcast_message(self.construct_message("draw", {'singlePath': single_path}))
63 | self.db_client.set(self.construct_key(self.room_name, self.page_no), self.paths)
64 |
65 | elif event == "clear":
66 | self.broadcast_message(self.construct_message("clear"))
67 | self.db_client.delete(self.construct_key(self.room_name, self.page_no))
68 |
69 | elif event == "get-image":
70 | if self.room_name != data['room'] or self.page_no != data['page']:
71 | self.logger.warning("Room name %s and/or page no. %s doesn't match with current room name %s and/or",
72 | "page no. %s. Ignoring" % (
73 | data['room'], data['page'], self.room_name, self.page_no))
74 | image_url, width, height = self.get_image_data(self.room_name, self.page_no)
75 | self.send_message(self.construct_message("image", {'url': image_url,
76 | 'width': width, 'height': height}))
77 |
78 | elif event == "video":
79 | make_video(self.construct_key(self.room_name, self.page_no))
80 |
81 | elif event == "new-page":
82 | self.logger.info("num_pages was %d" % self.num_pages)
83 | self.db_client.set(self.construct_key("info", self.room_name, "npages"),
84 | str(self.num_pages + 1))
85 | self.num_pages += 1
86 | self.logger.info("num_pages is now %d" % self.num_pages)
87 | self.init(self.room_name, self.num_pages)
88 |
89 | # @Override
90 | def on_close(self):
91 | self.leave_room(self.room_name)
92 |
93 | ## Higher lever methods
94 | def init(self, room_name, page_no):
95 | self.logger.info("Initializing %s and %s" % (room_name, page_no))
96 |
97 | self.room_name = room_name
98 | self.page_no = page_no
99 | self.join_room(self.room_name)
100 |
101 | n_pages = self.db_client.get(self.construct_key("info", self.room_name, "npages"))
102 | if n_pages:
103 | self.num_pages = int(n_pages)
104 | # First send the image if it exists
105 | image_url, width, height = self.get_image_data(self.room_name, self.page_no)
106 | self.send_message(self.construct_message("image", {'url': image_url,
107 | 'width': width, 'height': height}))
108 | # Then send the paths
109 | p = self.db_client.get(self.construct_key(self.room_name, self.page_no))
110 | if p:
111 | self.paths = json.loads(p)
112 | else:
113 | self.paths = []
114 | self.logger.info("No data in database")
115 | self.send_message(self.construct_message("draw-many",
116 | {'datas': self.paths, 'npages': self.num_pages}))
117 |
118 |
119 |
120 | def leave_room(self, room_name, clear_paths=True):
121 | self.logger.info("Leaving room %s" % room_name)
122 | self.pubsub_client.unsubscribe(self.construct_key(room_name, self.page_no), self)
123 | if clear_paths:
124 | self.paths = []
125 |
126 | def join_room(self, room_name):
127 | self.logger.info("Joining room %s" % room_name)
128 | self.pubsub_client.subscribe(self.construct_key(room_name, self.page_no), self)
129 |
130 | ## Messaging related methods
131 | def construct_key(self, namespace, key, *keys):
132 | return ":".join([str(namespace), str(key)] + list(map(str, keys)))
133 |
134 | def construct_message(self, event, data={}):
135 | m = json.dumps({"event": event, "data": data})
136 | return m
137 |
138 | def broadcast_message(self, message):
139 | self.pubsub_client.publish(self.construct_key(self.room_name, self.page_no), message, self)
140 |
141 | def send_message(self, message):
142 | message = b64encode(compress(bytes(quote(str(message)), 'utf-8'), 9))
143 | self.write_message(message)
144 |
145 | def get_image_data(self, room_name, page_no):
146 | image_url = os.path.join("files", room_name, str(page_no) + "_image.png")
147 | image_path = os.path.join(config.ROOT_DIR, image_url)
148 | try:
149 | image = read(image_path)
150 | except IOError as e:
151 | self.logger.error("Error %s while reading image at location %s" % (e,
152 | image_path))
153 | return '', -1, -1
154 | width, height = image.size
155 | return image_url, width, height
156 |
--------------------------------------------------------------------------------
/org/collabdraw/pubsub/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
--------------------------------------------------------------------------------
/org/collabdraw/pubsub/pubsubclientfactory.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import logging
4 |
5 | from .redispubsubclient import RedisPubSubClient
6 | from .pubsubclienttypes import PubSubClientTypes
7 | from .pubsubinterface import PubSubInterface
8 |
9 |
10 | class PubSubClientFactory:
11 | @staticmethod
12 | def getPubSubClient(pubsub_client_type_str):
13 | """
14 | @param pubsub_client_type_str:
15 | @rtype : PubSubInterface
16 | """
17 | logger = logging.getLogger('websocket')
18 | logger.info("Initializing with pubsub client type %s" % pubsub_client_type_str)
19 | if pubsub_client_type_str == PubSubClientTypes.redis:
20 | return RedisPubSubClient()
21 | else:
22 | raise RuntimeError("Unknown pubsub type %s" % pubsub_client_type_str)
--------------------------------------------------------------------------------
/org/collabdraw/pubsub/pubsubclienttypes.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 |
4 | class PubSubClientTypes:
5 | redis = "redis"
6 | in_memory = "in-memory"
--------------------------------------------------------------------------------
/org/collabdraw/pubsub/pubsubinterface.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | class PubSubInterface():
4 | def subscribe(self, topic, listener):
5 | raise RuntimeError("PubSub Interface method %s not implemented" % "subscribe")
6 |
7 | def unsubscribe(self, topic, listener):
8 | raise RuntimeError("PubSub Interface method %s not implemented" % "unsubscribe")
9 |
10 | def publish(self, topic, message, publisher):
11 | raise RuntimeError("PubSub Interface method %s not implemented" % "publish")
12 |
13 |
--------------------------------------------------------------------------------
/org/collabdraw/pubsub/redispubsubclient.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import logging
4 | import threading
5 |
6 | import redis
7 |
8 | import config
9 | from .pubsubinterface import PubSubInterface
10 |
11 |
12 | # TODOS
13 | ## Thread pooling
14 |
15 | class RedisPubSubClient(PubSubInterface):
16 |
17 | redis_client = redis.from_url(config.REDIS_URL)
18 |
19 | def __init__(self):
20 | self.logger = logging.getLogger('websocket')
21 | self.pubsub_client = self.redis_client.pubsub()
22 | self.logger.info("Initialized redis pubsub client")
23 |
24 | def subscribe(self, topic, listener):
25 | self.logger.debug("Subscribing to topic %s" % topic)
26 | self.pubsub_client.subscribe(topic)
27 | self.t = threading.Thread(target=self._redis_listener, args=(topic, listener, self.pubsub_client))
28 | self.t.start()
29 |
30 | def unsubscribe(self, topic, listener):
31 | self.logger.debug("Unsubscribing from topic %s" % topic)
32 |
33 | if self.t:
34 | self.pubsub_client.unsubscribe(topic)
35 | self.t.join(60)
36 |
37 | def publish(self, topic, message, publisher):
38 | self.logger.debug("Publishing to topic %s" % topic)
39 | # TODO If publisher is subscribed to topic
40 | self.redis_client.publish(topic, message)
41 |
42 | def _redis_listener(self, topic, listener, pubsub_client):
43 | self.logger.info("Starting listener thread for topic %s" % topic)
44 | for message in pubsub_client.listen():
45 | self.logger.debug("Sending message to topic %s" % topic)
46 | if message['type'] == 'message':
47 | listener.send_message(message['data'].decode('utf-8'))
48 |
--------------------------------------------------------------------------------
/org/collabdraw/tools/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
--------------------------------------------------------------------------------
/org/collabdraw/tools/tools.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import config
3 | import hashlib
4 | import os
5 | import glob
6 |
7 | import cairo
8 |
9 |
10 | def createCairoContext(w, h):
11 | surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
12 | ctx = cairo.Context(surface)
13 | ctx.set_source_rgb(255, 255, 255)
14 | ctx.rectangle(0, 0, w, h)
15 | ctx.fill()
16 | return ctx
17 |
18 |
19 | def hexColorToRGB(colorstring):
20 | logger = logging.getLogger('websocket')
21 | """ convert #RRGGBB to an (R, G, B) tuple """
22 | colorstring = colorstring.strip()
23 | if colorstring == "black":
24 | return (0, 0, 0)
25 | elif colorstring == "blue":
26 | return (0, 0, 255)
27 | elif colorstring == "green":
28 | return (0, 255, 0)
29 | elif colorstring == "red":
30 | return (255, 0, 0)
31 | logger.debug("Converting string %s to rgb" % colorstring)
32 | if colorstring[0] == '#': colorstring = colorstring[1:]
33 | if len(colorstring) != 6:
34 | logger.error("input #%s is not in #RRGGBB format" % colorstring)
35 | return (0, 0, 0)
36 | r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
37 | r, g, b = [int(n, 16) for n in (r, g, b)]
38 | logger.debug("Returning %d, %d, %d" % (r, g, b))
39 | return (r, g, b)
40 |
41 |
42 | def hash_password(password):
43 | s = config.HASH_SALT + password
44 | return hashlib.md5(s.encode("utf-8")).hexdigest()
45 |
46 | def delete_files(pattern):
47 | """
48 | Works only for files, not directories
49 | """
50 | filelist = glob.glob(pattern)
51 | for f in filelist:
52 | os.remove(f)
53 |
--------------------------------------------------------------------------------
/org/collabdraw/tools/uploadprocessor.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import subprocess
4 | import glob
5 | import os
6 | import logging
7 |
8 | import config
9 | from ..dbclient.dbclientfactory import DbClientFactory
10 | from org.collabdraw.tools.tools import delete_files
11 |
12 |
13 | def process_uploaded_file(dir_path, fname, key):
14 | logger = logging.getLogger('websocket')
15 | db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
16 |
17 | file_path = os.path.join(dir_path, fname)
18 | logger.info("Processing file %s" % file_path)
19 | # Split the pdf files by pages
20 | subprocess.call(['pdfseparate', file_path, dir_path + '/%d_image.pdf'])
21 | # Convert the pdf files to png
22 | subprocess.call(['mogrify', '-format', 'png', '--', dir_path + '/*image.pdf'])
23 | # Delete all the files
24 | delete_files(dir_path + '/*image.pdf')
25 | logger.info("Finished processing file")
26 | # Insert the number of pages processed for that room
27 | db_key = "info:%s:npages" % key
28 | db_client.set(db_key, len(glob.glob(dir_path + '/*.png')))
--------------------------------------------------------------------------------
/org/collabdraw/tools/videomaker.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import os
4 | import subprocess
5 | import json
6 | import uuid
7 | import logging
8 |
9 | import config
10 | from ..tools.tools import hexColorToRGB, createCairoContext
11 | from ..dbclient.dbclientfactory import DbClientFactory
12 | from ..tools.tools import delete_files
13 |
14 |
15 | def make_video(key):
16 | logger = logging.getLogger('websocket')
17 | db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
18 |
19 | p = db_client.get(key)
20 | tmp_path = os.path.abspath("./tmp")
21 | os.makedirs(tmp_path, exist_ok=True)
22 | path_prefix = os.path.join(tmp_path, str(uuid.uuid4()))
23 | if p:
24 | points = json.loads(p)
25 | i = 0
26 | c = createCairoContext(920, 550)
27 | for point in points:
28 | c.set_line_width(float(point['lineWidth'].replace('px', '')))
29 | c.set_source_rgb(*hexColorToRGB(point['lineColor']))
30 | if point['type'] == 'dragstart' or point['type'] == 'touchstart':
31 | c.move_to(point['oldx'], point['oldy'])
32 | elif point['type'] == 'drag' or point['type'] == 'touchmove':
33 | c.move_to(point['oldx'], point['oldy'])
34 | c.line_to(point['x'], point['y'])
35 | c.stroke()
36 | f = open(path_prefix + "_img_" + str(i) + ".png", "wb")
37 | c.get_target().write_to_png(f)
38 | f.close()
39 | i += 1
40 | video_file_name = path_prefix + '_video.mp4'
41 | retval = subprocess.call(['ffmpeg', '-f', 'image2', '-i', path_prefix + '_img_%d.png', video_file_name])
42 | logger.info("Image for key %s successfully created. File name is %s" % (key, video_file_name))
43 | if retval == 0:
44 | # Clean up if successful
45 | cleanup_files = path_prefix + '_img_*'
46 | logger.info("Cleaning up %s" % cleanup_files)
47 | delete_files(cleanup_files)
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | []()
2 |
3 |
4 | ABOUT:
5 | -------------
6 | Collabdraw is an open-source online whiteboard application built to work on desktops and tablets
7 | alike. The user interface is HTML5 (using enyojs), and the backend runs on python tornado and redis.
8 |
9 |
10 | FEATURES:
11 | -------------
12 | 1. Works on most tablets out of the box, interface designed for touch interfaces
13 | 2. Multiple rooms, pages for collaboration
14 | 3. Take quick snapshots of the board
15 | 4. Upload pdf and annotate on whiteboard
16 | 5. Support for SSL, and authentication
17 | 6. Fast, handles lots of users simultaneously
18 | 7. **Runs on heroku out of the box!**
19 |
20 | SERVER REQUIREMENTS:
21 | -------------
22 | 1. Python 3.2+
23 | 2. Redis server
24 | 3. All python packages specified in requirements.txt (which might involve installing other
25 | non-python dependencies like cairo, redis)
26 | 4. libpoppler (for pdfseparate), imagemagick (for mogrify) for upload functionality
27 | 5. ffmpeg to enable video functionality
28 |
29 | INSTALLATION:
30 | -------------
31 | 1. Install all system requirements. If on Ubuntu/Debian do:
32 | ```
33 | apt-get install python3 redis-server poppler-utils imagemagick ffmpeg python3-pip git pkg-config libcairo2-dev
34 | ```
35 |
36 | 2. Clone the git repository
37 | ```
38 | git clone git://github.com/anandtrex/collabdraw.git
39 | ```
40 |
41 | 3. Initialize submodules to get enyojs libraries.
42 | ```
43 | cd collabdraw
44 | git submodule init
45 | git submodule update
46 | ```
47 |
48 | 4. Install python library requirements
49 | ```
50 | pip-3.2 install virtualenv
51 | virtualenv venv
52 | source venv/bin/activate
53 | pip install -r requirements.txt
54 | ```
55 |
56 | 5. Set the hostnames, ports and other options in config.py. Most of the options are explained in the
57 | config file. You need the url that points to your redis server, and the url that points to your
58 | websocket endpoint
59 |
60 | 6. Test if your setup works
61 | ```
62 | collabdraw> ./run_tests.sh
63 | ```
64 |
65 | RUNNING:
66 | -------------
67 | 1. Start the redis server (On Ubuntu/Debian, on most setups, this is started automatically on installation)
68 | 2. Run `python main.py`
69 |
70 |
71 | HEROKU DEPLOYMENT:
72 | --------------------
73 | 1. Create a [heroku](http://heroku.com) account, create an app, and add the "Redis cloud" plugin.
74 | Install heroku toolbelt on your box. Login to heroku with `heroku login`. You can follow the
75 | instructions on the [heroku quickstart page](https://devcenter.heroku.com/articles/quickstart)
76 | 2. Clone the git repository `git clone git://github.com/anandtrex/collabdraw.git && cd collabdraw`
77 | 3. Edit config.py to point to your app. If you use the "Redis cloud" heroku addon, you can leave the
78 | redis url as it is.
79 | 4. Add your app as a remote in git with:
80 | ```
81 | heroku git:remote -a
82 | ```
83 | 5. Run `./set_heroku_path.sh` to set the LD_LIBRARY_PATH in your heroku app config to point properly
84 | to ffmpeg
85 | 6. Run:
86 | ```
87 | git push heroku master
88 | ```
89 | 7. Profit!!!
90 | 8. You can check your heroku installation by logging into the heroku dynamo with `heroku run bash`, and running `./run_tests.sh`. If this passes, all's good.
91 |
92 |
93 |
94 | NOTES:
95 | -------------
96 | * All rooms are currently "public". Anyone registered user can join any room, if they know the room name. Private rooms on the way.
97 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | decorator==3.4.0
2 | pystacia==0.1
3 | redis==2.7.2
4 | six==1.2.0
5 | tornado==2.4.1
6 | git+http://anongit.freedesktop.org/git/pycairo
7 | nose==1.3.0
8 |
--------------------------------------------------------------------------------
/resource/css/upload.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Verdana, Arial, sans-serif;
3 | font-size: 90%;
4 | }
5 | h1, h2, h3, h4 {
6 | margin-top: 0px;
7 | }
8 | div.row {
9 | margin-bottom: 10px;
10 | }
11 | *:focus {
12 | outline: none;
13 | }
14 | .floatLeft {
15 | float: left;
16 | }
17 | .floatRight {
18 | float: right;
19 | }
20 | .clear {
21 | clear: both;
22 | }
23 |
24 | form {
25 | padding: 20px;
26 | border: 1px solid #cccccc;
27 | border-radius: 10px;
28 | -moz-border-radius: 10px;
29 | -webkit-box-shadow: 0 0 10px #ccc;
30 | -moz-box-shadow: 0 0 10px #ccc;
31 | box-shadow: 0 0 10px #ccc;
32 | width: 400px;
33 | margin: 20px auto;
34 | background-image: -moz-linear-gradient(top, #ffffff,#f2f2f2);
35 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f2f2f2));
36 | }
37 |
38 | input {
39 | border: 1px solid #ccc;
40 | font-size: 13pt;
41 | padding: 5px 10px 5px 10px;
42 | border-radius: 10px;
43 | -moz-border-radius: 10px;
44 | -webkit-transition: all 0.5s ease-in-out;
45 | -moz-transition: all 0.5s ease-in-out;
46 | transition: all 0.5s ease-in-out;
47 | }
48 |
49 | input[type=button] {
50 | background-image: -moz-linear-gradient(top, #ffffff, #dfdfdf);
51 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#dfdfdf));
52 | }
53 |
54 | input:focus {
55 | -webkit-box-shadow: 0 0 10px #ccc;
56 | -moz-box-shadow: 0 0 10px #ccc;
57 | box-shadow: 0 0 5px #ccc;
58 | -webkit-transform: scale(1.05);
59 | -moz-transform: scale(1.05);
60 | transform: scale(1.05);
61 | }
62 |
63 | #fileToUpload {
64 | width: 378px;
65 | }
66 |
67 | #progressIndicator {
68 | font-size: 10pt;
69 | }
70 |
71 | #fileInfo {
72 | font-size: 10pt;
73 | font-style: italic;
74 | color: #aaa;
75 | margin-top: 10px;
76 | }
77 |
78 | #progressBar {
79 | height: 14px;
80 | border: 1px solid #cccccc;
81 | display: none;
82 | border-radius: 10px;
83 | -moz-border-radius: 10px;
84 | background-image: -moz-linear-gradient(top, #66cc00, #4b9500);
85 | background-image: -webkit-gradient(linear, left top, left bottom, from(#66cc00), to(#4b9500));
86 | }
87 |
88 | #uploadResponse {
89 | margin-top: 10px;
90 | padding: 20px;
91 | overflow: hidden;
92 | display: none;
93 | border-radius: 10px;
94 | -moz-border-radius: 10px;
95 | border: 1px solid #ccc;
96 | box-shadow: 0 0 5px #ccc;
97 | background-image: -moz-linear-gradient(top, #ff9900, #c77801);
98 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ff9900), to(#c77801));
99 | }
--------------------------------------------------------------------------------
/resource/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CollabDraw
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/resource/html/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CollabDraw
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resource/html/register.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CollabDraw
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resource/html/upload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CollabDraw
7 |
8 |
9 |
10 |
11 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/resource/js/App.js:
--------------------------------------------------------------------------------
1 | enyo.kind({
2 | name: "App",
3 | kind: "FittableRows",
4 | fit: true,
5 |
6 | published: {
7 | whiteboard: '',
8 | curves: {
9 | color: 'black',
10 | width: '3px',
11 | },
12 | uid: 'test',
13 | room: 'one',
14 | canvasWidth: 1000,
15 | canvasHeight: 550,
16 | appIpAddress: "",
17 | appPort: "",
18 | },
19 |
20 | components: [{
21 | kind: "FittableRows",
22 | fit: true,
23 | style: "text-align: center; padding: 20px; background-color: #F0F0F0; z-index: 0",
24 | components: [{
25 | style: "margin: auto; background-color: #FFFFFF;",
26 | ondragstart: "touchstart",
27 | ondragover: "touchmove",
28 | ondragfinish: "touchend",
29 | name: "canvasContainer",
30 | rendered: function() {
31 | this.applyStyle("width", this.owner.canvasWidth + "px");
32 | this.applyStyle("height", this.owner.canvasHeight + "px");
33 | if (window.location.protocol == 'https:') {
34 | var websocketAddress = 'wss://' + this.owner.appIpAddress + ':' + this.owner.appPort + '/realtime/';
35 | } else {
36 | var websocketAddress = 'ws://' + this.owner.appIpAddress + ':' + this.owner.appPort + '/realtime/';
37 | }
38 | if (this.hasNode()) {
39 | var _this = this;
40 | this.owner.$.loadingPopup.show();
41 | this.owner.whiteboard = new WhiteboardSvg(this.node.getAttribute("id"), this.owner.canvasWidth, this.owner.canvasHeight, this.owner.uid, this.owner.room, 1, websocketAddress, function(numPages, currentPage) {
42 | _this.owner.$.currentPage.setMax(numPages);
43 | _this.owner.$.currentPage.setValue(currentPage);
44 | _this.owner.$.loadingPopup.hide();
45 | });
46 | }
47 | },
48 | }],
49 | }, {
50 | kind: "onyx.MoreToolbar",
51 | components: [{
52 | kind: "onyx.Button",
53 | content: "Eraser",
54 | ontap: "selectEraser"
55 | }, {
56 | kind: "onyx.Button",
57 | content: "Pen",
58 | ontap: "selectPen"
59 | }, {
60 | kind: "onyx.PickerDecorator",
61 | components: [{
62 | name: "colorPicker",
63 | style: "background-color: black; color:black",
64 | content: "C",
65 | }, {
66 | kind: "onyx.Picker",
67 | onChange: "colorItemSelected",
68 | components: [{
69 | name: "red",
70 | style: "background-color: red; color: red",
71 | content: "C",
72 | }, {
73 | name: "blue",
74 | style: "background-color: blue; color: blue",
75 | content: "C",
76 | }, {
77 | name: "green",
78 | style: "background-color: green; color: green",
79 | content: "C",
80 | }, {
81 | name: "black",
82 | style: "background-color: black; color:black",
83 | content: "C",
84 | }, ]
85 | }, ],
86 | }, {
87 | kind: "onyx.MenuDecorator",
88 | onSelect: "optionSelected",
89 | components: [{
90 | content: "More Options..."
91 | }, {
92 | kind: "onyx.Menu",
93 | components: [{
94 | name: "clear",
95 | content: "Clear",
96 | }, {
97 | name: "createJoinRoom",
98 | content: "Create/Join Room",
99 | popup: "createJoinRoomPopup",
100 | }, {
101 | name: "getVideo",
102 | content: "Get Video...",
103 | }, {
104 | name: "exportToSvg",
105 | content: "Export to SVG",
106 | }, {
107 | name: "upload",
108 | content: "Upload",
109 | }, ]
110 | }, ]
111 | }, {
112 | kind: "onyx.Button",
113 | content: "Previous",
114 | ontap: "selectPrevious"
115 | }, {
116 | kind: "onyx.Button",
117 | content: "New Page",
118 | ontap: "selectNewPage"
119 | }, {
120 | kind: "onyx.Button",
121 | content: "Next",
122 | ontap: "selectNext"
123 | }, {
124 | style: "width: 35%",
125 | content: " "
126 | }, {
127 | kind: "onyx.PickerDecorator",
128 | components: [{}, {
129 | kind: "onyx.IntegerPicker",
130 | name: "currentPage",
131 | onSelect: "gotoPage",
132 | min: 1,
133 | }, ],
134 | }, {
135 | kind: "onyx.Button",
136 | content: "Logout",
137 | ontap: "logout"
138 | }, {
139 | name: "createJoinRoomPopup",
140 | kind: "onyx.Popup",
141 | centered: true,
142 | modal: true,
143 | floating: true,
144 | style: "width: 300px; height: 200px; padding: 0 20px 5px 20px",
145 | components: [{
146 | content: "Enter Room name
",
147 | allowHtml: true,
148 | }, {
149 | content: "If your room doesn't exist already in your account, it will be created",
150 | }, {
151 | kind: "onyx.InputDecorator",
152 | style: "margin: 10px 10px 10px 0; width: 250px",
153 | alwaysLooksFocused: true,
154 | components: [{
155 | kind: "onyx.Input",
156 | name: "roomName",
157 | }]
158 | }, {
159 | tag: "br"
160 | }, {
161 | kind: "onyx.Button",
162 | content: "Cancel",
163 | ontap: "selectCreateJoinRoomPopupCancel",
164 | }, {
165 | kind: "onyx.Button",
166 | content: "OK",
167 | ontap: "selectCreateJoinRoomPopupOk",
168 | popup: "lightPopup",
169 | style: "margin-left: 10px",
170 | }],
171 | }, {
172 | name: "loadingPopup",
173 | kind: "onyx.Popup",
174 | centered: true,
175 | autoDismiss: false,
176 | modal: true,
177 | floating: true,
178 | components: [{
179 | kind: "onyx.Spinner"
180 | }, ],
181 | }]
182 | }, ],
183 |
184 | drawRectangle: function(inSender, inEvent) {
185 | this.whiteboard.drawRectangle();
186 | },
187 |
188 | touchstart: function(inSender, inEvent) {
189 | var canvasBounds = this.$.canvasContainer.getBounds();
190 | this.curves.oldx = inEvent.pageX - canvasBounds.left;
191 | this.curves.oldy = inEvent.pageY - canvasBounds.top;
192 | this.whiteboard.startPath(this.curves.oldx, this.curves.oldy, this.curves.color, this.curves.width, true);
193 | },
194 |
195 | touchmove: function(inSender, inEvent) {
196 | if (this.curves.oldx != -1 && this.curves.oldy != -1) {
197 | var canvasBounds = this.$.canvasContainer.getBounds();
198 | x = inEvent.pageX - canvasBounds.left;
199 | y = inEvent.pageY - canvasBounds.top;
200 | this.whiteboard.continuePath(this.curves.oldx, this.curves.oldy, x, y, this.curves.color, this.curves.width, true);
201 | this.curves.oldx = x;
202 | this.curves.oldy = y;
203 | }
204 | },
205 |
206 | touchend: function(inSender, inEvent) {
207 | if (this.curves.oldx != -1 && this.curves.oldy != -1) {
208 | var canvasBounds = this.$.canvasContainer.getBounds();
209 | x = inEvent.pageX - canvasBounds.left;
210 | y = inEvent.pageY - canvasBounds.top;
211 | this.whiteboard.endPath(this.curves.oldx, this.curves.oldy, x, y, this.curves.color, this.curves.width, true);
212 | this.curves.oldx = -1;
213 | this.curves.oldy = -1;
214 | }
215 | },
216 |
217 | selectEraser: function(inSender, inEvent) {
218 | this.curves.color = '#ffffff';
219 | this.curves.width = '10px';
220 | },
221 |
222 | selectPen: function(inSender, inEvent) {
223 | this.curves.color = '#000000';
224 | this.curves.width = '3px';
225 | },
226 |
227 | optionSelected: function(inSender, inEvent) {
228 | var name = inEvent.originator.name;
229 | switch (name) {
230 | case "clear":
231 | this.selectClear(inSender, inEvent);
232 | break;
233 | case "createJoinRoom":
234 | this.selectCreateJoinRoom(inSender, inEvent);
235 | break;
236 | case "getVideo":
237 | this.selectGetVideo(inSender, inEvent);
238 | break;
239 | case "exportToSvg":
240 | this.selectExportToSvg(inSender, inEvent);
241 | break;
242 | case "upload":
243 | this.selectUpload(inSender, inEvent);
244 | break;
245 | }
246 | },
247 |
248 | colorItemSelected: function(inSender, inEvent) {
249 | var color = inEvent.selected.name;
250 | this.$.colorPicker.applyStyle("background-color", color);
251 | this.$.colorPicker.applyStyle("color", color);
252 | this.curves.color = color;
253 | this.curves.width = "3px";
254 | },
255 |
256 | selectClear: function(inSender, inEvent) {
257 | this.whiteboard.clear(true);
258 | },
259 |
260 | selectCreateJoinRoom: function(inSender, inEvent) {
261 | var p = this.$[inEvent.originator.popup];
262 | if (p) {
263 | p.show();
264 | }
265 | },
266 |
267 | selectGetVideo: function(inSender, inEvent) {
268 | this.whiteboard.makeVideo();
269 | },
270 |
271 | selectExportToSvg: function(inSender, inEvent) {
272 | var svg = document.getElementsByTagName('svg')[0];
273 | var svg_xml = (new XMLSerializer).serializeToString(svg);
274 | window.open("data:image/svg+xml;base64," + btoa(svg_xml), "Export");
275 | },
276 |
277 | selectUpload: function(inSender, inEvent) {
278 | window.location = "./upload?room=" + this.room;
279 | },
280 |
281 | selectNext: function(inSender, inEvent) {
282 | this.$.loadingPopup.show();
283 | var result = this.whiteboard.nextPage();
284 | this.updatePageInfo();
285 | if (!result) this.$.loadingPopup.hide();
286 | },
287 |
288 | selectPrevious: function(inSender, inEvent) {
289 | this.$.loadingPopup.show();
290 | var result = this.whiteboard.prevPage();
291 | this.updatePageInfo();
292 | if (!result) this.$.loadingPopup.hide();
293 | },
294 |
295 | selectCreateJoinRoomPopupCancel: function(inSender, inEvent) {
296 | this.$.createJoinRoomPopup.hide();
297 | },
298 |
299 | selectCreateJoinRoomPopupOk: function(inSender, inEvent) {
300 | var value = this.$.roomName.getValue();
301 | if (value) {
302 | this.whiteboard.joinRoom(value);
303 | }
304 | this.$.createJoinRoomPopup.hide();
305 | },
306 | logout: function() {
307 | window.location = "./logout.html";
308 | },
309 |
310 | selectNewPage: function(inSender, inEvent) {
311 | this.whiteboard.newPage();
312 | this.updatePageInfo();
313 | },
314 |
315 | updatePageInfo: function() {
316 | this.$.currentPage.setMax(this.whiteboard.getNumPages());
317 | this.$.currentPage.setValue(this.whiteboard.getCurrentPage());
318 | },
319 |
320 | gotoPage: function(inSender, inEvent) {
321 | this.whiteboard.gotoPage(inEvent.selected.content);
322 | },
323 |
324 | });
325 |
--------------------------------------------------------------------------------
/resource/js/Connection.js:
--------------------------------------------------------------------------------
1 | enyo.kind({
2 | name: 'Connection',
3 | kind: null,
4 |
5 | socket: 'undefined',
6 | whiteboard: 'undefined',
7 | singlePath: [],
8 | currentPathLength: 0,
9 | uid: 'uid',
10 | room: 'undefined',
11 | page: 1,
12 |
13 | constructor: function(address, whiteboard, room) {
14 | this.whiteboard = whiteboard;
15 | //console.log("Connecting to address " + address);
16 | this.socket = new WebSocket(address);
17 | this.room = room;
18 | this.page = 1;
19 | //console.log("Room is " + room);
20 |
21 | _this = this;
22 | this.socket.onmessage = function(evt) {
23 | message = JXG.decompress(evt.data);
24 | message = JSON.parse(message);
25 | evnt = message['event'];
26 | data = message['data'];
27 | switch (evnt) {
28 | case 'ready':
29 | _this.init(_this.uid, _this.room, _this.page);
30 | break;
31 | case 'draw':
32 | _this.remoteDraw(_this, data);
33 | break;
34 | case 'draw-many':
35 | _this.remoteDrawMany(_this, data);
36 | break;
37 | case 'clear':
38 | _this.remoteClear(_this, data);
39 | break;
40 | case 'image':
41 | _this.remoteImage(_this, data);
42 | break;
43 | }
44 | }
45 | },
46 |
47 | sendMessage: function(evt, data) {
48 | message = JSON.stringify({
49 | "event": evt,
50 | "data": data
51 | });
52 | this.socket.send(message);
53 | },
54 |
55 | init: function(uid, room, currentPage) {
56 | console.log("Sending init for room " + room + " and page " + currentPage);
57 | this.whiteboard.clear(false, false);
58 | this.sendMessage("init", {
59 | "room": room,
60 | "page": currentPage
61 | });
62 | },
63 |
64 | /**
65 | * Get data from server to initialize this whiteboard
66 | * @param {Object} uid
67 | * @param {Object} room
68 | * @param {Object} page
69 | */
70 | joinRoom: function(room, page) {
71 | this.room = room;
72 | this.singlePath = [];
73 | this.currentPathLength = 0;
74 | this.whiteboard.clear(false, false);
75 | //console.log("Sending init for room " + room);
76 | this.sendMessage("init", {
77 | "room": this.room
78 | });
79 | },
80 |
81 | /**
82 | * Send a single path (segment) to the server
83 | * @param {x, y, type, lineColor, lineWidth} a point on the path
84 | */
85 | sendPath: function(data) {
86 | this.singlePath.push(data);
87 | this.currentPathLength++;
88 |
89 | // Send path every two points or when user removes finger
90 | if (this.currentPathLength > 2 || data.type === "touchend") {
91 | this.sendMessage("draw-click", {
92 | "singlePath": this.singlePath
93 | });
94 | this.singlePath = [];
95 | this.currentPathLength = 0;
96 | }
97 | },
98 |
99 | /**
100 | * Clear all other canvases (in the same room on the same page)
101 | */
102 | sendClear: function() {
103 | this.singlePath = [];
104 | this.currentPathLength = 0;
105 | this.sendMessage("clear", {});
106 | },
107 |
108 | getImage: function() {
109 | //console.log("Getting image for page " + this.page);
110 | this.sendMessage("get-image", {
111 | "room": this.room,
112 | "page": this.page
113 | });
114 | },
115 |
116 | /**
117 | * Make video remotely
118 | */
119 | makeVideo: function() {
120 | this.sendMessage("video", {});
121 | },
122 |
123 | /*
124 | * Create a new page
125 | */
126 | newPage: function() {
127 | this.whiteboard.clear(false, false);
128 | this.sendMessage("new-page", {});
129 | },
130 |
131 | /***
132 | * All remote functions below
133 | */
134 |
135 | /**
136 | * Draw from realtime data incoming from server
137 | * Called when server sends @event 'draw'
138 | * @param {Object} self
139 | * @param {singlePath: [points...]} input
140 | */
141 | remoteDraw: function(self, input) {
142 | var sPath = input.singlePath;
143 | var data = {};
144 | // point on path
145 | for (d in sPath) {
146 | data = sPath[d];
147 | if (data == null) continue;
148 | if (data.type == 'touchstart') self.whiteboard.startPath(data.oldx, data.oldy, data.lineColor, data.lineWidth, false);
149 | else if (data.type == 'touchmove') self.whiteboard.continuePath(data.oldx, data.oldy, data.x, data.y, data.lineColor, data.lineWidth, false);
150 | else if (data.type == 'touchend') self.whiteboard.endPath(data.oldx, data.oldy, data.x, data.y, data.lineColor, data.lineWidth, false);
151 | }
152 | },
153 |
154 | /**
155 | * Draw from stored data incoming from server
156 | * Called when server sends @event 'draw-many'
157 | * @param {Object} self
158 | * @param {datas:[points...]} data
159 | */
160 | remoteDrawMany: function(self, data) {
161 | ds = data.datas;
162 | for (d in ds) {
163 | if (ds[d] === null) continue;
164 | if (ds[d].type == 'touchstart') self.whiteboard.startPath(ds[d].oldx, ds[d].oldy, ds[d].lineColor, ds[d].lineWidth, false);
165 | else if (ds[d].type == 'touchmove') self.whiteboard.continuePath(ds[d].oldx, ds[d].oldy, ds[d].x, ds[d].y, ds[d].lineColor, ds[d].lineWidth, false);
166 | else if (ds[d].type == 'touchend') self.whiteboard.endPath(ds[d].oldx, ds[d].oldy, ds[d].x, ds[d].y, ds[d].lineColor, ds[d].lineWidth, false);
167 | }
168 | //console.log("Total pages is " + data.npages);
169 | self.whiteboard.setTotalPages(data.npages);
170 | },
171 |
172 | /**
173 | * Clear from server
174 | * Called when server sends @event 'clear'
175 | * @param {Object} self
176 | * @param {Object} data
177 | */
178 | remoteClear: function(self, data) {
179 | self.whiteboard.clear(false);
180 | },
181 |
182 | remoteImage: function(self, data) {
183 | if (data.url != "") {
184 | var img = document.createElement('img');
185 | img.src = data.url;
186 | //console.log("Image url is " + data.url);
187 | self.whiteboard.loadImage(data.url, data.width, data.height);
188 | }
189 | },
190 |
191 | });
192 |
--------------------------------------------------------------------------------
/resource/js/Login.js:
--------------------------------------------------------------------------------
1 | enyo.kind({
2 | name: "Login",
3 | kind: "FittableRows",
4 | fit: true,
5 |
6 | published: {
7 | uid: 'test',
8 | room: 'one',
9 | },
10 |
11 | components: [{
12 | kind: "FittableRows",
13 | fit: true,
14 | components: [{
15 | kind: "FittableColumns",
16 | style: "padding: 20px; z-index: 0; margin-top: 50px;",
17 | classes: "enyo-center",
18 | width: '500px',
19 | height: '500px',
20 | fit: false,
21 | components: [{
22 | kind: "onyx.Groupbox",
23 | components: [{
24 | kind: "onyx.GroupboxHeader",
25 | content: "Please Login"
26 | }, {
27 | kind: "onyx.InputDecorator",
28 | style: "text-align: left",
29 | components: [{
30 | content: "Login ID ",
31 | style: "margin-right: 15px",
32 | }, {
33 | kind: "onyx.Input",
34 | name: "loginId",
35 | style: "float: right",
36 | }, ],
37 | }, {
38 | kind: "onyx.InputDecorator",
39 | style: "text-align: left",
40 | components: [{
41 | content: "Password ",
42 | style: "margin-right: 15px",
43 | }, {
44 | kind: "onyx.Input",
45 | name: "loginPassword",
46 | type: "password",
47 | style: "float: right",
48 | }],
49 | }, {
50 | kind: "FittableColumns",
51 | classes: "enyo-center",
52 | style: "padding: 15px",
53 | components: [{
54 | kind: "onyx.Button",
55 | style: "margin: 5px 5px 5px auto",
56 | content: "Login",
57 | ontap: "login",
58 | name: "loginButton",
59 | }, {
60 | kind: "onyx.Button",
61 | style: "margin: 5px auto 5px 5px",
62 | content: "Back",
63 | ontap: "goToApp"
64 | }, {
65 | kind: "onyx.Button",
66 | style: "margin: 5px auto 5px 5px",
67 | content: "Register",
68 | ontap: "goToRegister"
69 | }, ],
70 | }, {
71 | kind: "FittableRows",
72 | showing: false,
73 | name: "loginResult",
74 | style: "padding: 10px",
75 | components: [{
76 | name: "loginStatus",
77 | allowHtml: true,
78 | content: "",
79 | }, ],
80 | }, ],
81 | }, ],
82 | }, ],
83 | }, {
84 | kind: "onyx.Toolbar",
85 | components: [{
86 | kind: "onyx.Button",
87 | content: "Back",
88 | ontap: "goToApp"
89 | }, ],
90 | }],
91 |
92 | goToApp: function() {
93 | window.location = "./index.html";
94 | },
95 |
96 | goToRegister: function() {
97 | window.location = "./register.html";
98 | },
99 |
100 | login: function() {
101 | var loginid = this.$.loginId.getValue();
102 | var password = this.$.loginPassword.getValue();
103 | console.log("User id and password are: " + loginid + ", " + password);
104 | var ajax = new enyo.Ajax({
105 | method: "POST",
106 | postBody: {
107 | url: "./login.html",
108 | },
109 | contentType: "application/json",
110 | });
111 | ajax.go({
112 | loginId: loginid,
113 | loginPassword: password,
114 | });
115 | ajax.response(this, "handleLoginResponse");
116 | },
117 |
118 | handleLoginResponse: function(inSender, inResponse) {
119 | console.log("Response");
120 | console.log(JSON.stringify(inResponse));
121 | if (inResponse.result == "success") {
122 | window.location = "./index.html";
123 | } else if (inResponse.result == "failure") {
124 | this.$.loginStatus.setContent("Wrong username/password");
125 | } else {
126 | this.$.loginStatus.setContent("Error occurred. Try again.");
127 | }
128 | this.$.loginResult.show();
129 | },
130 | });
131 |
--------------------------------------------------------------------------------
/resource/js/MessageEvent.js:
--------------------------------------------------------------------------------
1 | Ext.define('Whiteboard.MessageEvent', {
2 | extend: 'Ext.util.Observable',
3 | constructor: function(config){
4 | this.addEvents({
5 | "showMessage" : true,
6 | });
7 |
8 | // Copy configured listeners into *this* object so
9 | // that the base class's constructor will add them.
10 | this.listeners = config.listeners;
11 |
12 | // Call our superclass constructor to complete
13 | // construction process.
14 | this.callParent(arguments)
15 | }
16 | });
--------------------------------------------------------------------------------
/resource/js/Register.js:
--------------------------------------------------------------------------------
1 | enyo.kind({
2 | name: "Register",
3 | kind: "FittableRows",
4 | fit: true,
5 |
6 | published: {
7 | uid: 'test',
8 | room: 'one',
9 | },
10 |
11 | components: [{
12 | kind: "FittableRows",
13 | fit: true,
14 | components: [{
15 | kind: "FittableColumns",
16 | style: "padding: 20px; z-index: 0; margin-top: 50px;",
17 | classes: "enyo-center",
18 | width: '500px',
19 | height: '500px',
20 | fit: false,
21 | components: [{
22 | kind: "onyx.Groupbox",
23 | components: [{
24 | kind: "onyx.GroupboxHeader",
25 | content: "Please Register"
26 | }, {
27 | kind: "onyx.InputDecorator",
28 | style: "text-align: left",
29 | components: [{
30 | content: "Login ID ",
31 | style: "margin-right: 15px",
32 | }, {
33 | kind: "onyx.Input",
34 | name: "loginId",
35 | style: "float: right",
36 | }, ],
37 | }, {
38 | kind: "onyx.InputDecorator",
39 | style: "text-align: left",
40 | components: [{
41 | content: "Password ",
42 | style: "margin-right: 15px",
43 | }, {
44 | kind: "onyx.Input",
45 | name: "loginPassword",
46 | type: "password",
47 | style: "float: right",
48 | }],
49 | }, {
50 | kind: "onyx.InputDecorator",
51 | style: "text-align: left",
52 | components: [{
53 | content: "Re-enter Password ",
54 | style: "margin-right: 15px",
55 | }, {
56 | kind: "onyx.Input",
57 | name: "loginPasswordRepeat",
58 | type: "password",
59 | style: "float: right",
60 | }],
61 | }, {
62 | kind: "FittableColumns",
63 | classes: "enyo-center",
64 | style: "padding: 15px",
65 | components: [{
66 | kind: "onyx.Button",
67 | style: "margin: 5px 5px 5px auto",
68 | content: "Register",
69 | ontap: "register",
70 | name: "registerButton",
71 | }, {
72 | kind: "onyx.Button",
73 | style: "margin: 5px auto 5px 5px",
74 | content: "Back",
75 | ontap: "goToApp"
76 | }, ],
77 | }, {
78 | kind: "FittableRows",
79 | showing: false,
80 | name: "registerResult",
81 | style: "padding: 10px",
82 | components: [{
83 | name: "registerStatus",
84 | allowHtml: true,
85 | content: "",
86 | }, ],
87 | }, ],
88 | }, ],
89 | }, ],
90 | }, {
91 | kind: "onyx.Toolbar",
92 | components: [{
93 | kind: "onyx.Button",
94 | content: "Back",
95 | ontap: "goToApp"
96 | }, ],
97 | }],
98 |
99 | goToApp: function() {
100 | window.location = "./index.html";
101 | },
102 |
103 | register: function() {
104 | var loginid = this.$.loginId.getValue();
105 | var password = this.$.loginPassword.getValue();
106 | var repeatPassword = this.$.loginPasswordRepeat.getValue();
107 | if (password != repeatPassword) {
108 | this.$.registerStatus.setContent("Passwords don't match");
109 | this.$.registerResult.show();
110 | return;
111 | }
112 | var ajax = new enyo.Ajax({
113 | method: "POST",
114 | postBody: {
115 | url: "./register.html",
116 | },
117 | contentType: "application/json",
118 | });
119 | ajax.go({
120 | loginId: loginid,
121 | loginPassword: password,
122 | });
123 | ajax.response(this, "handleLoginResponse");
124 | console.log("User id and password are: " + loginid + ", " + password);
125 | },
126 |
127 | handleLoginResponse: function(inSender, inResponse) {
128 | console.log(JSON.stringify(inResponse));
129 | if (inResponse.result == "success") {
130 | window.location = "./index.html";
131 | } else if (inResponse.result == "conflict") {
132 | this.$.registerStatus.setContent("Login ID already exists");
133 | } else {
134 | this.$.registerStatus.setContent("Error occurred. Try again.");
135 | }
136 | this.$.registerResult.show();
137 | },
138 | });
139 |
--------------------------------------------------------------------------------
/resource/js/Svg.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This contains all the local functions to interact with the whiteboard. It also contains
3 | * interfaces to the Connection class.
4 | */
5 | enyo.kind({
6 | name: 'WhiteboardSvg',
7 | kind: null,
8 |
9 | cvs: 'undefined',
10 | currentPage: 1,
11 | totalPages: -1,
12 | uid: "",
13 | room: "",
14 | connection: 'undefined',
15 | callback: 'undefined',
16 |
17 | getNumPages: function() {
18 | return this.totalPages;
19 | },
20 |
21 | getCurrentPage: function() {
22 | return this.currentPage;
23 | },
24 |
25 | constructor: function(name, width, height, uid, room, page, websocketAddress, callback) {
26 | this.uid = uid;
27 | this.room = room;
28 | this.cvs = new Raphael(name, width, height);
29 | this.connection = new Connection(websocketAddress, this, room);
30 | this.callback = callback;
31 | },
32 |
33 | /**
34 | * Join specified room
35 | * @param {Object} room
36 | */
37 | joinRoom: function(room) {
38 | this.room = room;
39 | this.connection.joinRoom(room);
40 | },
41 |
42 | /**
43 | * Getter for cvs
44 | */
45 | getCanvas: function() {
46 | return this.cvs;
47 | },
48 |
49 | /**
50 | * Called when user starts a path
51 | * @param {Object} x
52 | * @param {Object} y
53 | * @param {Object} send
54 | */
55 | startPath: function(x, y, lc, lw, send) {
56 | if (send) {
57 | this.connection.sendPath({
58 | oldx: x,
59 | oldy: y,
60 | type: 'touchstart',
61 | lineColor: lc,
62 | lineWidth: lw,
63 | });
64 | }
65 | },
66 |
67 | /**
68 | * Called when user continues path (without lifting finger)
69 | */
70 | continuePath: function(oldx, oldy, x, y, lc, lw, send) {
71 | this.drawAndSendPath('touchmove', oldx, oldy, x, y, lc, lw, send)
72 | },
73 |
74 | /**
75 | * Called when user lifts finger
76 | */
77 | endPath: function(oldx, oldy, x, y, lc, lw, send) {
78 | this.drawAndSendPath('touchend', oldx, oldy, x, y, lc, lw, send)
79 | },
80 |
81 | drawAndSendPath: function(type, oldx, oldy, x, y, lc, lw, send) {
82 | path = "M " + oldx + " " + oldy + " L " + x + " " + y + " Z";
83 | var p = this.cvs.path(path);
84 | p.attr("stroke", lc);
85 | p.attr("stroke-width", lw)
86 | if (send) {
87 | this.connection.sendPath({
88 | oldx: oldx,
89 | oldy: oldy,
90 | x: x,
91 | y: y,
92 | type: type,
93 | lineColor: lc,
94 | lineWidth: lw,
95 | });
96 | }
97 | },
98 |
99 | /**
100 | * Clear canvas
101 | * @param {Object} send
102 | */
103 | clear: function(send, reloadImage) {
104 | reloadImage = typeof reloadImage == 'undefined' ? true : reloadImage;
105 | this.cvs.clear();
106 | if (reloadImage) this.connection.getImage();
107 | if (send) this.connection.sendClear();
108 | },
109 |
110 | /**
111 | * Load an image onto the canvas
112 | * @param {Object} url
113 | */
114 | loadImage: function(url, width, height) {
115 | this.cvs.image(url, 5, 5, width, height);
116 | },
117 |
118 | getImage: function() {
119 | images = document.getElementsByTagName("image");
120 | // TODO More specific targetting of image
121 | if (images.length != 0) {
122 | for (var i = 0; i < images.length; i++) {
123 | images[i].parentNode.removeChild(images[i]);
124 | }
125 | }
126 | this.connection.getImage(this.currentPage);
127 | },
128 |
129 | /**
130 | * Go to the next page
131 | */
132 | nextPage: function() {
133 | if (this.currentPage + 1 > this.totalPages) {
134 | // Blank canvas
135 | return false;
136 | } else {
137 | this.currentPage += 1;
138 | this.connection.init(this.uid, this.room, this.currentPage);
139 | return true;
140 | }
141 | },
142 |
143 | /**
144 | * Go to the previous page
145 | */
146 | prevPage: function() {
147 |
148 | if (this.currentPage - 1 <= 0) {
149 | // do nothing
150 | return false;
151 | } else {
152 | this.currentPage -= 1;
153 | this.connection.init(this.uid, this.room, this.currentPage);
154 | return true;
155 | }
156 | },
157 |
158 | gotoPage: function(pageNum) {
159 | this.connection.init(this.uid, this.room, pageNum);
160 | this.currentPage = pageNum;
161 | },
162 |
163 | newPage: function() {
164 | this.currentPage = this.totalPages + 1;
165 | this.totalPages += 1;
166 | this.connection.newPage(this.uid, this.room, this.currentPage);
167 | },
168 |
169 | getColor: function() {
170 | return this.color;
171 | },
172 |
173 | setTotalPages: function(pages) {
174 | this.totalPages = pages;
175 | this.callback(this.totalPages, this.currentPage);
176 | },
177 |
178 | /**
179 | * Ask server to make video of current whiteboard
180 | */
181 | makeVideo: function() {
182 | this.connection.makeVideo();
183 | },
184 |
185 | drawRectangle: function() {
186 | this.cvs.rect(10, 10, 50, 50);
187 | },
188 | });
189 |
--------------------------------------------------------------------------------
/resource/js/Upload.js:
--------------------------------------------------------------------------------
1 | enyo.kind({
2 | name: "Upload",
3 | kind: "FittableRows",
4 | fit: true,
5 |
6 | published: {
7 | uid: 'test',
8 | room: 'one',
9 | },
10 |
11 | components: [{
12 | kind: "FittableRows",
13 | fit: true,
14 | components: [{
15 | kind: "FittableColumns",
16 | style: "padding: 20px; z-index: 0; margin-top: 50px;",
17 | classes: "enyo-center",
18 | width: '500px',
19 | height: '500px',
20 | fit: false,
21 | components: [{
22 | kind: "onyx.Groupbox",
23 | components: [{
24 | kind: "onyx.GroupboxHeader",
25 | content: "Select a pdf file to upload"
26 | }, {
27 | content: "Warning: This will replace the file for this room"
28 | }, {
29 | kind: "onyx.InputDecorator",
30 | components: [{
31 | kind: "enyo.Input",
32 | type: "file",
33 | name: "fileInput",
34 | onchange: "fileSelected",
35 | }],
36 | }, {
37 | kind: "FittableColumns",
38 | classes: "enyo-center",
39 | style: "padding: 15px",
40 | components: [{
41 | kind: "onyx.Button",
42 | style: "margin: 5px 5px 5px auto",
43 | content: "Upload",
44 | ontap: "uploadFile",
45 | name: "uploadButton",
46 | }, {
47 | kind: "onyx.Button",
48 | style: "margin: 5px auto 5px 5px",
49 | content: "Back",
50 | ontap: "goToApp"
51 | }, ],
52 | }, {
53 | kind: "onyx.ProgressBar",
54 | progress: 0,
55 | name: "fileUploadProgress",
56 | showing: false,
57 | }, {
58 | kind: "FittableRows",
59 | showing: false,
60 | name: "fileInfo",
61 | style: "padding: 10px",
62 | components: [{
63 | name: "fileName",
64 | style: "color:#808080",
65 | }, {
66 | name: "fileSize",
67 | style: "color:#808080",
68 | }, {
69 | name: "fileType",
70 | style: "color:#808080",
71 | }, {
72 | name: "uploadStatus",
73 | allowHtml: true,
74 | content: "Press the upload button to upload",
75 | }, ],
76 | }, ],
77 |
78 | }, ],
79 | }, ],
80 | }, {
81 | kind: "onyx.Toolbar",
82 | components: [{
83 | kind: "onyx.Button",
84 | content: "Back",
85 | ontap: "goToApp"
86 | }, ],
87 | }],
88 |
89 | goToApp: function() {
90 | window.location = "./index.html";
91 | },
92 |
93 | uploadFile: function(inSender, inEvent) {
94 |
95 | var progressBar = this.$.fileUploadProgress;
96 | var uploadStatus = this.$.uploadStatus;
97 |
98 | uploadStatus.setContent("Uploading file");
99 | progressBar.show()
100 |
101 | var xhr = new XMLHttpRequest();
102 | xhr.upload.addEventListener('progress', function(ev) {
103 | progressBar.setProgress((ev.loaded * 100 / ev.total));
104 | }, false);
105 |
106 | xhr.onreadystatechange = function(ev) {
107 | if (this.readyState == this.DONE) {
108 | progressBar.setProgress(100);
109 | uploadStatus.setContent("File uploaded.
Press back to go back to main page");
110 | }
111 | };
112 | xhr.open('POST', "./upload", true);
113 | var file = this.$.fileInput.node.files[0];
114 | var data = new FormData();
115 | data.append('room', 'one');
116 | data.append('file', file);
117 | xhr.send(data);
118 | },
119 |
120 | fileSelected: function(inSender, inEvent) {
121 | this.$.uploadButton.setDisabled(false);
122 | this.$.uploadStatus.setContent("Press the upload button to upload");
123 | var file = this.$.fileInput.node.files[0];
124 |
125 | var fileSize = 0;
126 | if (file.size > 1024 * 1024) fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
127 | else fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
128 |
129 | console.log("File name is: " + file.name);
130 | this.$.fileName.setContent('Name: ' + file.name);
131 | this.$.fileSize.setContent('Size: ' + fileSize);
132 | this.$.fileType.setContent('Type: ' + file.type);
133 |
134 | if (file.type != "application/pdf") {
135 | this.$.uploadStatus.setContent("Only pdf files allowed
Please select a pdf file");
136 | this.$.uploadButton.setDisabled(true);
137 | }
138 |
139 | this.$.fileInfo.show();
140 | },
141 | });
142 |
--------------------------------------------------------------------------------
/resource/js/jsxcompressor.js:
--------------------------------------------------------------------------------
1 | JXG = {exists: (function(undefined){return function(v){return !(v===undefined || v===null);}})()};
2 | JXG.decompress = function(str) {return unescape((new JXG.Util.Unzip(JXG.Util.Base64.decodeAsArray(str))).unzip()[0][0]);};
3 | /*
4 | Copyright 2008-2012
5 | Matthias Ehmann,
6 | Michael Gerhaeuser,
7 | Carsten Miller,
8 | Bianca Valentin,
9 | Alfred Wassermann,
10 | Peter Wilfahrt
11 |
12 | This file is part of JSXGraph.
13 |
14 | Dual licensed under the Apache License Version 2.0, or LGPL Version 3 licenses.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with JSXCompressor. If not, see .
18 |
19 | You should have received a copy of the Apache License along with JSXCompressor.
20 | If not, see .
21 |
22 | */
23 |
24 | /**
25 | * @fileoverview Utilities for uncompressing and base64 decoding
26 | */
27 |
28 | /**
29 | * @class Util class
30 | * Class for gunzipping, unzipping and base64 decoding of files.
31 | * It is used for reading GEONExT, Geogebra and Intergeo files.
32 | *
33 | * Only Huffman codes are decoded in gunzip.
34 | * The code is based on the source code for gunzip.c by Pasi Ojala
35 | * @see http://www.cs.tut.fi/~albert/Dev/gunzip/gunzip.c
36 | * @see http://www.cs.tut.fi/~albert
37 | */
38 | JXG.Util = {};
39 |
40 | /**
41 | * Unzip zip files
42 | */
43 | JXG.Util.Unzip = function (barray){
44 | var outputArr = [],
45 | output = "",
46 | debug = false,
47 | gpflags,
48 | files = 0,
49 | unzipped = [],
50 | crc,
51 | buf32k = new Array(32768),
52 | bIdx = 0,
53 | modeZIP=false,
54 |
55 | CRC, SIZE,
56 |
57 | bitReverse = [
58 | 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
59 | 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
60 | 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
61 | 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
62 | 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
63 | 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
64 | 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
65 | 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
66 | 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
67 | 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
68 | 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
69 | 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
70 | 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
71 | 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
72 | 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
73 | 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
74 | 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
75 | 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
76 | 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
77 | 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
78 | 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
79 | 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
80 | 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
81 | 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
82 | 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
83 | 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
84 | 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
85 | 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
86 | 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
87 | 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
88 | 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
89 | 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
90 | ],
91 |
92 | cplens = [
93 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
94 | 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
95 | ],
96 |
97 | cplext = [
98 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
99 | 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99
100 | ], /* 99==invalid */
101 |
102 | cpdist = [
103 | 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d,
104 | 0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1,
105 | 0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01,
106 | 0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001
107 | ],
108 |
109 | cpdext = [
110 | 0, 0, 0, 0, 1, 1, 2, 2,
111 | 3, 3, 4, 4, 5, 5, 6, 6,
112 | 7, 7, 8, 8, 9, 9, 10, 10,
113 | 11, 11, 12, 12, 13, 13
114 | ],
115 |
116 | border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15],
117 |
118 | bA = barray,
119 |
120 | bytepos=0,
121 | bitpos=0,
122 | bb = 1,
123 | bits=0,
124 |
125 | NAMEMAX = 256,
126 |
127 | nameBuf = [],
128 |
129 | fileout;
130 |
131 | function readByte(){
132 | bits+=8;
133 | if (bytepos");
136 | return bA[bytepos++];
137 | } else
138 | return -1;
139 | };
140 |
141 | function byteAlign(){
142 | bb = 1;
143 | };
144 |
145 | function readBit(){
146 | var carry;
147 | bits++;
148 | carry = (bb & 1);
149 | bb >>= 1;
150 | if (bb==0){
151 | bb = readByte();
152 | carry = (bb & 1);
153 | bb = (bb>>1) | 0x80;
154 | }
155 | return carry;
156 | };
157 |
158 | function readBits(a) {
159 | var res = 0,
160 | i = a;
161 |
162 | while(i--) {
163 | res = (res<<1) | readBit();
164 | }
165 | if(a) {
166 | res = bitReverse[res]>>(8-a);
167 | }
168 | return res;
169 | };
170 |
171 | function flushBuffer(){
172 | //document.write('FLUSHBUFFER:'+buf32k);
173 | bIdx = 0;
174 | };
175 | function addBuffer(a){
176 | SIZE++;
177 | //CRC=updcrc(a,crc);
178 | buf32k[bIdx++] = a;
179 | outputArr.push(String.fromCharCode(a));
180 | //output+=String.fromCharCode(a);
181 | if(bIdx==0x8000){
182 | //document.write('ADDBUFFER:'+buf32k);
183 | bIdx=0;
184 | }
185 | };
186 |
187 | function HufNode() {
188 | this.b0=0;
189 | this.b1=0;
190 | this.jump = null;
191 | this.jumppos = -1;
192 | };
193 |
194 | var LITERALS = 288;
195 |
196 | var literalTree = new Array(LITERALS);
197 | var distanceTree = new Array(32);
198 | var treepos=0;
199 | var Places = null;
200 | var Places2 = null;
201 |
202 | var impDistanceTree = new Array(64);
203 | var impLengthTree = new Array(64);
204 |
205 | var len = 0;
206 | var fpos = new Array(17);
207 | fpos[0]=0;
208 | var flens;
209 | var fmax;
210 |
211 | function IsPat() {
212 | while (1) {
213 | if (fpos[len] >= fmax)
214 | return -1;
215 | if (flens[fpos[len]] == len)
216 | return fpos[len]++;
217 | fpos[len]++;
218 | }
219 | };
220 |
221 | function Rec() {
222 | var curplace = Places[treepos];
223 | var tmp;
224 | if (debug)
225 | document.write("
len:"+len+" treepos:"+treepos);
226 | if(len==17) { //war 17
227 | return -1;
228 | }
229 | treepos++;
230 | len++;
231 |
232 | tmp = IsPat();
233 | if (debug)
234 | document.write("
IsPat "+tmp);
235 | if(tmp >= 0) {
236 | curplace.b0 = tmp; /* leaf cell for 0-bit */
237 | if (debug)
238 | document.write("
b0 "+curplace.b0);
239 | } else {
240 | /* Not a Leaf cell */
241 | curplace.b0 = 0x8000;
242 | if (debug)
243 | document.write("
b0 "+curplace.b0);
244 | if(Rec())
245 | return -1;
246 | }
247 | tmp = IsPat();
248 | if(tmp >= 0) {
249 | curplace.b1 = tmp; /* leaf cell for 1-bit */
250 | if (debug)
251 | document.write("
b1 "+curplace.b1);
252 | curplace.jump = null; /* Just for the display routine */
253 | } else {
254 | /* Not a Leaf cell */
255 | curplace.b1 = 0x8000;
256 | if (debug)
257 | document.write("
b1 "+curplace.b1);
258 | curplace.jump = Places[treepos];
259 | curplace.jumppos = treepos;
260 | if(Rec())
261 | return -1;
262 | }
263 | len--;
264 | return 0;
265 | };
266 |
267 | function CreateTree(currentTree, numval, lengths, show) {
268 | var i;
269 | /* Create the Huffman decode tree/table */
270 | //document.write("
createtree
");
271 | if (debug)
272 | document.write("currentTree "+currentTree+" numval "+numval+" lengths "+lengths+" show "+show);
273 | Places = currentTree;
274 | treepos=0;
275 | flens = lengths;
276 | fmax = numval;
277 | for (i=0;i<17;i++)
278 | fpos[i] = 0;
279 | len = 0;
280 | if(Rec()) {
281 | //fprintf(stderr, "invalid huffman tree\n");
282 | if (debug)
283 | alert("invalid huffman tree\n");
284 | return -1;
285 | }
286 | if (debug){
287 | document.write('
Tree: '+Places.length);
288 | for (var a=0;a<32;a++){
289 | document.write("Places["+a+"].b0="+Places[a].b0+"
");
290 | document.write("Places["+a+"].b1="+Places[a].b1+"
");
291 | }
292 | }
293 |
294 | /*if(show) {
295 | var tmp;
296 | for(tmp=currentTree;tmpjump?tmp->jump-currentTree:0,(tmp->jump?tmp->jump-currentTree:0)*6+0xcf0);
298 | if(!(tmp.b0 & 0x8000)) {
299 | //fprintf(stdout, " 0x%03x (%c)", tmp->b0,(tmp->b0<256 && isprint(tmp->b0))?tmp->b0:'�');
300 | }
301 | if(!(tmp.b1 & 0x8000)) {
302 | if((tmp.b0 & 0x8000))
303 | fprintf(stdout, " ");
304 | fprintf(stdout, " 0x%03x (%c)", tmp->b1,(tmp->b1<256 && isprint(tmp->b1))?tmp->b1:'�');
305 | }
306 | fprintf(stdout, "\n");
307 | }
308 | }*/
309 | return 0;
310 | };
311 |
312 | function DecodeValue(currentTree) {
313 | var len, i,
314 | xtreepos=0,
315 | X = currentTree[xtreepos],
316 | b;
317 |
318 | /* decode one symbol of the data */
319 | while(1) {
320 | b=readBit();
321 | if (debug)
322 | document.write("b="+b);
323 | if(b) {
324 | if(!(X.b1 & 0x8000)){
325 | if (debug)
326 | document.write("ret1");
327 | return X.b1; /* If leaf node, return data */
328 | }
329 | X = X.jump;
330 | len = currentTree.length;
331 | for (i=0;i>1);
429 | if(j > 23) {
430 | j = (j<<1) | readBit(); /* 48..255 */
431 |
432 | if(j > 199) { /* 200..255 */
433 | j -= 128; /* 72..127 */
434 | j = (j<<1) | readBit(); /* 144..255 << */
435 | } else { /* 48..199 */
436 | j -= 48; /* 0..151 */
437 | if(j > 143) {
438 | j = j+136; /* 280..287 << */
439 | /* 0..143 << */
440 | }
441 | }
442 | } else { /* 0..23 */
443 | j += 256; /* 256..279 << */
444 | }
445 | if(j < 256) {
446 | addBuffer(j);
447 | //document.write("out:"+String.fromCharCode(j));
448 | /*fprintf(errfp, "@%d %02x\n", SIZE, j);*/
449 | } else if(j == 256) {
450 | /* EOF */
451 | break;
452 | } else {
453 | var len, dist;
454 |
455 | j -= 256 + 1; /* bytes + EOF */
456 | len = readBits(cplext[j]) + cplens[j];
457 |
458 | j = bitReverse[readBits(5)]>>3;
459 | if(cpdext[j] > 8) {
460 | dist = readBits(8);
461 | dist |= (readBits(cpdext[j]-8)<<8);
462 | } else {
463 | dist = readBits(cpdext[j]);
464 | }
465 | dist += cpdist[j];
466 |
467 | /*fprintf(errfp, "@%d (l%02x,d%04x)\n", SIZE, len, dist);*/
468 | for(j=0;jparam: "+literalCodes+" "+distCodes+" "+lenCodes+"
");
484 | for(j=0; j<19; j++) {
485 | ll[j] = 0;
486 | }
487 |
488 | // Get the decode tree code lengths
489 |
490 | //document.write("
");
491 | for(j=0; jll:'+ll);
497 | len = distanceTree.length;
498 | for (i=0; idistanceTree");
506 | for(var a=0;a"+distanceTree[a].b0+" "+distanceTree[a].b1+" "+distanceTree[a].jump+" "+distanceTree[a].jumppos);
508 | /*if (distanceTree[a].jumppos!=-1)
509 | document.write(" "+distanceTree[a].jump.b0+" "+distanceTree[a].jump.b1);
510 | */
511 | }
512 | }
513 | //document.write('
tree created');
514 |
515 | //read in literal and distance code lengths
516 | n = literalCodes + distCodes;
517 | i = 0;
518 | var z=-1;
519 | if (debug)
520 | document.write("
n="+n+" bits: "+bits+"
");
521 | while(i < n) {
522 | z++;
523 | j = DecodeValue(distanceTree);
524 | if (debug)
525 | document.write("
"+z+" i:"+i+" decode: "+j+" bits "+bits+"
");
526 | if(j<16) { // length of code in bits (0..15)
527 | ll[i++] = j;
528 | } else if(j==16) { // repeat last length 3 to 6 times
529 | var l;
530 | j = 3 + readBits(2);
531 | if(i+j > n) {
532 | flushBuffer();
533 | return 1;
534 | }
535 | l = i ? ll[i-1] : 0;
536 | while(j--) {
537 | ll[i++] = l;
538 | }
539 | } else {
540 | if(j==17) { // 3 to 10 zero length codes
541 | j = 3 + readBits(3);
542 | } else { // j == 18: 11 to 138 zero length codes
543 | j = 11 + readBits(7);
544 | }
545 | if(i+j > n) {
546 | flushBuffer();
547 | return 1;
548 | }
549 | while(j--) {
550 | ll[i++] = 0;
551 | }
552 | }
553 | }
554 | /*for(j=0; jliteralTree");
581 | while(1) {
582 | j = DecodeValue(literalTree);
583 | if(j >= 256) { // In C64: if carry set
584 | var len, dist;
585 | j -= 256;
586 | if(j == 0) {
587 | // EOF
588 | break;
589 | }
590 | j--;
591 | len = readBits(cplext[j]) + cplens[j];
592 |
593 | j = DecodeValue(distanceTree);
594 | if(cpdext[j] > 8) {
595 | dist = readBits(8);
596 | dist |= (readBits(cpdext[j]-8)<<8);
597 | } else {
598 | dist = readBits(cpdext[j]);
599 | }
600 | dist += cpdist[j];
601 | while(len--) {
602 | var c = buf32k[(bIdx - dist) & 0x7fff];
603 | addBuffer(c);
604 | }
605 | } else {
606 | addBuffer(j);
607 | }
608 | }
609 | }
610 | } while(!last);
611 | flushBuffer();
612 |
613 | byteAlign();
614 | return 0;
615 | };
616 |
617 | JXG.Util.Unzip.prototype.unzipFile = function(name) {
618 | var i;
619 | this.unzip();
620 | //alert(unzipped[0][1]);
621 | for (i=0;i");
648 | }
649 | */
650 | //alert(bA);
651 | nextFile();
652 | return unzipped;
653 | };
654 |
655 | function nextFile(){
656 | if (debug)
657 | alert("NEXTFILE");
658 | outputArr = [];
659 | var tmp = [];
660 | modeZIP = false;
661 | tmp[0] = readByte();
662 | tmp[1] = readByte();
663 | if (debug)
664 | alert("type: "+tmp[0]+" "+tmp[1]);
665 | if (tmp[0] == parseInt("78",16) && tmp[1] == parseInt("da",16)){ //GZIP
666 | if (debug)
667 | alert("GEONExT-GZIP");
668 | DeflateLoop();
669 | if (debug)
670 | alert(outputArr.join(''));
671 | unzipped[files] = new Array(2);
672 | unzipped[files][0] = outputArr.join('');
673 | unzipped[files][1] = "geonext.gxt";
674 | files++;
675 | }
676 | if (tmp[0] == parseInt("1f",16) && tmp[1] == parseInt("8b",16)){ //GZIP
677 | if (debug)
678 | alert("GZIP");
679 | //DeflateLoop();
680 | skipdir();
681 | if (debug)
682 | alert(outputArr.join(''));
683 | unzipped[files] = new Array(2);
684 | unzipped[files][0] = outputArr.join('');
685 | unzipped[files][1] = "file";
686 | files++;
687 | }
688 | if (tmp[0] == parseInt("50",16) && tmp[1] == parseInt("4b",16)){ //ZIP
689 | modeZIP = true;
690 | tmp[2] = readByte();
691 | tmp[3] = readByte();
692 | if (tmp[2] == parseInt("3",16) && tmp[3] == parseInt("4",16)){
693 | //MODE_ZIP
694 | tmp[0] = readByte();
695 | tmp[1] = readByte();
696 | if (debug)
697 | alert("ZIP-Version: "+tmp[1]+" "+tmp[0]/10+"."+tmp[0]%10);
698 |
699 | gpflags = readByte();
700 | gpflags |= (readByte()<<8);
701 | if (debug)
702 | alert("gpflags: "+gpflags);
703 |
704 | var method = readByte();
705 | method |= (readByte()<<8);
706 | if (debug)
707 | alert("method: "+method);
708 |
709 | readByte();
710 | readByte();
711 | readByte();
712 | readByte();
713 |
714 | var crc = readByte();
715 | crc |= (readByte()<<8);
716 | crc |= (readByte()<<16);
717 | crc |= (readByte()<<24);
718 |
719 | var compSize = readByte();
720 | compSize |= (readByte()<<8);
721 | compSize |= (readByte()<<16);
722 | compSize |= (readByte()<<24);
723 |
724 | var size = readByte();
725 | size |= (readByte()<<8);
726 | size |= (readByte()<<16);
727 | size |= (readByte()<<24);
728 |
729 | if (debug)
730 | alert("local CRC: "+crc+"\nlocal Size: "+size+"\nlocal CompSize: "+compSize);
731 |
732 | var filelen = readByte();
733 | filelen |= (readByte()<<8);
734 |
735 | var extralen = readByte();
736 | extralen |= (readByte()<<8);
737 |
738 | if (debug)
739 | alert("filelen "+filelen);
740 | i = 0;
741 | nameBuf = [];
742 | while (filelen--){
743 | var c = readByte();
744 | if (c == "/" | c ==":"){
745 | i = 0;
746 | } else if (i < NAMEMAX-1)
747 | nameBuf[i++] = String.fromCharCode(c);
748 | }
749 | if (debug)
750 | alert("nameBuf: "+nameBuf);
751 |
752 | //nameBuf[i] = "\0";
753 | if (!fileout)
754 | fileout = nameBuf;
755 |
756 | var i = 0;
757 | while (i < extralen){
758 | c = readByte();
759 | i++;
760 | }
761 |
762 | CRC = 0xffffffff;
763 | SIZE = 0;
764 |
765 | if (size = 0 && fileOut.charAt(fileout.length-1)=="/"){
766 | //skipdir
767 | if (debug)
768 | alert("skipdir");
769 | }
770 | if (method == 8){
771 | DeflateLoop();
772 | if (debug)
773 | alert(outputArr.join(''));
774 | unzipped[files] = new Array(2);
775 | unzipped[files][0] = outputArr.join('');
776 | unzipped[files][1] = nameBuf.join('');
777 | files++;
778 | //return outputArr.join('');
779 | }
780 | skipdir();
781 | }
782 | }
783 | };
784 |
785 | function skipdir(){
786 | var crc,
787 | tmp = [],
788 | compSize, size, os, i, c;
789 |
790 | if ((gpflags & 8)) {
791 | tmp[0] = readByte();
792 | tmp[1] = readByte();
793 | tmp[2] = readByte();
794 | tmp[3] = readByte();
795 |
796 | if (tmp[0] == parseInt("50",16) &&
797 | tmp[1] == parseInt("4b",16) &&
798 | tmp[2] == parseInt("07",16) &&
799 | tmp[3] == parseInt("08",16))
800 | {
801 | crc = readByte();
802 | crc |= (readByte()<<8);
803 | crc |= (readByte()<<16);
804 | crc |= (readByte()<<24);
805 | } else {
806 | crc = tmp[0] | (tmp[1]<<8) | (tmp[2]<<16) | (tmp[3]<<24);
807 | }
808 |
809 | compSize = readByte();
810 | compSize |= (readByte()<<8);
811 | compSize |= (readByte()<<16);
812 | compSize |= (readByte()<<24);
813 |
814 | size = readByte();
815 | size |= (readByte()<<8);
816 | size |= (readByte()<<16);
817 | size |= (readByte()<<24);
818 |
819 | if (debug)
820 | alert("CRC:");
821 | }
822 |
823 | if (modeZIP)
824 | nextFile();
825 |
826 | tmp[0] = readByte();
827 | if (tmp[0] != 8) {
828 | if (debug)
829 | alert("Unknown compression method!");
830 | return 0;
831 | }
832 |
833 | gpflags = readByte();
834 | if (debug){
835 | if ((gpflags & ~(parseInt("1f",16))))
836 | alert("Unknown flags set!");
837 | }
838 |
839 | readByte();
840 | readByte();
841 | readByte();
842 | readByte();
843 |
844 | readByte();
845 | os = readByte();
846 |
847 | if ((gpflags & 4)){
848 | tmp[0] = readByte();
849 | tmp[2] = readByte();
850 | len = tmp[0] + 256*tmp[1];
851 | if (debug)
852 | alert("Extra field size: "+len);
853 | for (i=0;ihttp://www.webtoolkit.info/
904 | */
905 | JXG.Util.Base64 = {
906 |
907 | // private property
908 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
909 |
910 | // public method for encoding
911 | encode : function (input) {
912 | var output = [],
913 | chr1, chr2, chr3, enc1, enc2, enc3, enc4,
914 | i = 0;
915 |
916 | input = JXG.Util.Base64._utf8_encode(input);
917 |
918 | while (i < input.length) {
919 |
920 | chr1 = input.charCodeAt(i++);
921 | chr2 = input.charCodeAt(i++);
922 | chr3 = input.charCodeAt(i++);
923 |
924 | enc1 = chr1 >> 2;
925 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
926 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
927 | enc4 = chr3 & 63;
928 |
929 | if (isNaN(chr2)) {
930 | enc3 = enc4 = 64;
931 | } else if (isNaN(chr3)) {
932 | enc4 = 64;
933 | }
934 |
935 | output.push([this._keyStr.charAt(enc1),
936 | this._keyStr.charAt(enc2),
937 | this._keyStr.charAt(enc3),
938 | this._keyStr.charAt(enc4)].join(''));
939 | }
940 |
941 | return output.join('');
942 | },
943 |
944 | // public method for decoding
945 | decode : function (input, utf8) {
946 | var output = [],
947 | chr1, chr2, chr3,
948 | enc1, enc2, enc3, enc4,
949 | i = 0;
950 |
951 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
952 |
953 | while (i < input.length) {
954 |
955 | enc1 = this._keyStr.indexOf(input.charAt(i++));
956 | enc2 = this._keyStr.indexOf(input.charAt(i++));
957 | enc3 = this._keyStr.indexOf(input.charAt(i++));
958 | enc4 = this._keyStr.indexOf(input.charAt(i++));
959 |
960 | chr1 = (enc1 << 2) | (enc2 >> 4);
961 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
962 | chr3 = ((enc3 & 3) << 6) | enc4;
963 |
964 | output.push(String.fromCharCode(chr1));
965 |
966 | if (enc3 != 64) {
967 | output.push(String.fromCharCode(chr2));
968 | }
969 | if (enc4 != 64) {
970 | output.push(String.fromCharCode(chr3));
971 | }
972 | }
973 |
974 | output = output.join('');
975 |
976 | if (utf8) {
977 | output = JXG.Util.Base64._utf8_decode(output);
978 | }
979 | return output;
980 |
981 | },
982 |
983 | // private method for UTF-8 encoding
984 | _utf8_encode : function (string) {
985 | string = string.replace(/\r\n/g,"\n");
986 | var utftext = "";
987 |
988 | for (var n = 0; n < string.length; n++) {
989 |
990 | var c = string.charCodeAt(n);
991 |
992 | if (c < 128) {
993 | utftext += String.fromCharCode(c);
994 | }
995 | else if((c > 127) && (c < 2048)) {
996 | utftext += String.fromCharCode((c >> 6) | 192);
997 | utftext += String.fromCharCode((c & 63) | 128);
998 | }
999 | else {
1000 | utftext += String.fromCharCode((c >> 12) | 224);
1001 | utftext += String.fromCharCode(((c >> 6) & 63) | 128);
1002 | utftext += String.fromCharCode((c & 63) | 128);
1003 | }
1004 |
1005 | }
1006 |
1007 | return utftext;
1008 | },
1009 |
1010 | // private method for UTF-8 decoding
1011 | _utf8_decode : function (utftext) {
1012 | var string = [],
1013 | i = 0,
1014 | c = 0, c2 = 0, c3 = 0;
1015 |
1016 | while ( i < utftext.length ) {
1017 | c = utftext.charCodeAt(i);
1018 | if (c < 128) {
1019 | string.push(String.fromCharCode(c));
1020 | i++;
1021 | }
1022 | else if((c > 191) && (c < 224)) {
1023 | c2 = utftext.charCodeAt(i+1);
1024 | string.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63)));
1025 | i += 2;
1026 | }
1027 | else {
1028 | c2 = utftext.charCodeAt(i+1);
1029 | c3 = utftext.charCodeAt(i+2);
1030 | string.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)));
1031 | i += 3;
1032 | }
1033 | }
1034 | return string.join('');
1035 | },
1036 |
1037 | _destrip: function (stripped, wrap){
1038 | var lines = [], lineno, i,
1039 | destripped = [];
1040 |
1041 | if (wrap==null)
1042 | wrap = 76;
1043 |
1044 | stripped.replace(/ /g, "");
1045 | lineno = stripped.length / wrap;
1046 | for (i = 0; i < lineno; i++)
1047 | lines[i]=stripped.substr(i * wrap, wrap);
1048 | if (lineno != stripped.length / wrap)
1049 | lines[lines.length]=stripped.substr(lineno * wrap, stripped.length-(lineno * wrap));
1050 |
1051 | for (i = 0; i < lines.length; i++)
1052 | destripped.push(lines[i]);
1053 | return destripped.join('\n');
1054 | },
1055 |
1056 | decodeAsArray: function (input){
1057 | var dec = this.decode(input),
1058 | ar = [], i;
1059 | for (i=0;i255){
1076 | switch (c) {
1077 | case 8364: c=128;
1078 | break;
1079 | case 8218: c=130;
1080 | break;
1081 | case 402: c=131;
1082 | break;
1083 | case 8222: c=132;
1084 | break;
1085 | case 8230: c=133;
1086 | break;
1087 | case 8224: c=134;
1088 | break;
1089 | case 8225: c=135;
1090 | break;
1091 | case 710: c=136;
1092 | break;
1093 | case 8240: c=137;
1094 | break;
1095 | case 352: c=138;
1096 | break;
1097 | case 8249: c=139;
1098 | break;
1099 | case 338: c=140;
1100 | break;
1101 | case 381: c=142;
1102 | break;
1103 | case 8216: c=145;
1104 | break;
1105 | case 8217: c=146;
1106 | break;
1107 | case 8220: c=147;
1108 | break;
1109 | case 8221: c=148;
1110 | break;
1111 | case 8226: c=149;
1112 | break;
1113 | case 8211: c=150;
1114 | break;
1115 | case 8212: c=151;
1116 | break;
1117 | case 732: c=152;
1118 | break;
1119 | case 8482: c=153;
1120 | break;
1121 | case 353: c=154;
1122 | break;
1123 | case 8250: c=155;
1124 | break;
1125 | case 339: c=156;
1126 | break;
1127 | case 382: c=158;
1128 | break;
1129 | case 376: c=159;
1130 | break;
1131 | default:
1132 | break;
1133 | }
1134 | }
1135 | return c;
1136 | };
1137 |
1138 | /**
1139 | * Decoding string into utf-8
1140 | * @param {String} string to decode
1141 | * @return {String} utf8 decoded string
1142 | */
1143 | JXG.Util.utf8Decode = function(utftext) {
1144 | var string = [];
1145 | var i = 0;
1146 | var c = 0, c1 = 0, c2 = 0, c3;
1147 | if (!JXG.exists(utftext)) return '';
1148 |
1149 | while ( i < utftext.length ) {
1150 | c = utftext.charCodeAt(i);
1151 |
1152 | if (c < 128) {
1153 | string.push(String.fromCharCode(c));
1154 | i++;
1155 | } else if((c > 191) && (c < 224)) {
1156 | c2 = utftext.charCodeAt(i+1);
1157 | string.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63)));
1158 | i += 2;
1159 | } else {
1160 | c2 = utftext.charCodeAt(i+1);
1161 | c3 = utftext.charCodeAt(i+2);
1162 | string.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)));
1163 | i += 3;
1164 | }
1165 | };
1166 | return string.join('');
1167 | };
1168 |
1169 | /**
1170 | * Generate a random uuid.
1171 | * http://www.broofa.com
1172 | * mailto:robert@broofa.com
1173 | *
1174 | * Copyright (c) 2010 Robert Kieffer
1175 | * Dual licensed under the MIT and GPL licenses.
1176 | *
1177 | * EXAMPLES:
1178 | * >>> Math.uuid()
1179 | * "92329D39-6F5C-4520-ABFC-AAB64544E172"
1180 | */
1181 | JXG.Util.genUUID = function() {
1182 | // Private array of chars to use
1183 | var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''),
1184 | uuid = new Array(36), rnd=0, r;
1185 |
1186 | for (var i = 0; i < 36; i++) {
1187 | if (i==8 || i==13 || i==18 || i==23) {
1188 | uuid[i] = '-';
1189 | } else if (i==14) {
1190 | uuid[i] = '4';
1191 | } else {
1192 | if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0;
1193 | r = rnd & 0xf;
1194 | rnd = rnd >> 4;
1195 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
1196 | }
1197 | }
1198 |
1199 | return uuid.join('');
1200 | };
1201 |
1202 |
--------------------------------------------------------------------------------
/resource/js/package.js:
--------------------------------------------------------------------------------
1 | enyo.depends(
2 | "$lib/layout",
3 | "$lib/onyx",
4 | "App.js",
5 | "Upload.js",
6 | "Svg.js",
7 | "Connection.js",
8 | "Login.js",
9 | "Register.js"
10 | // Cannot have a trailing comma!
11 | );
12 |
--------------------------------------------------------------------------------
/resource/js/raphael-min.js:
--------------------------------------------------------------------------------
1 | // ┌────────────────────────────────────────────────────────────────────┐ \\
2 | // │ Raphaël 2.1.0 - JavaScript Vector Library │ \\
3 | // ├────────────────────────────────────────────────────────────────────┤ \\
4 | // │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
5 | // │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\
6 | // ├────────────────────────────────────────────────────────────────────┤ \\
7 | // │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
8 | // └────────────────────────────────────────────────────────────────────┘ \\
9 |
10 | (function(a){var b="0.3.4",c="hasOwnProperty",d=/[\.\/]/,e="*",f=function(){},g=function(a,b){return a-b},h,i,j={n:{}},k=function(a,b){var c=j,d=i,e=Array.prototype.slice.call(arguments,2),f=k.listeners(a),l=0,m=!1,n,o=[],p={},q=[],r=h,s=[];h=a,i=0;for(var t=0,u=f.length;tf*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bR(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;yd)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cq(){return this.x+q+this.y+q+this.width+" × "+this.height}function cp(){return this.x+q+this.y}function cb(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bH(b,c,d){b=a._path2curve(b),c=a._path2curve(c);var e,f,g,h,i,j,k,l,m,n,o=d?0:[];for(var p=0,q=b.length;p=0&&y<=1&&A>=0&&A<=1&&(d?n++:n.push({x:x.x,y:x.y,t1:y,t2:A}))}}return n}function bF(a,b){return bG(a,b,1)}function bE(a,b){return bG(a,b)}function bD(a,b,c,d,e,f,g,h){if(!(x(a,c)x(e,g)||x(b,d)x(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(!k)return;var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(n<+y(a,c).toFixed(2)||n>+x(a,c).toFixed(2)||n<+y(e,g).toFixed(2)||n>+x(e,g).toFixed(2)||o<+y(b,d).toFixed(2)||o>+x(b,d).toFixed(2)||o<+y(f,h).toFixed(2)||o>+x(f,h).toFixed(2))return;return{x:l,y:m}}}function bC(a,b,c,d,e,f,g,h,i){if(!(i<0||bB(a,b,c,d,e,f,g,h)n)k/=2,l+=(m1?1:i<0?0:i;var j=i/2,k=12,l=[-0.1252,.1252,-0.3678,.3678,-0.5873,.5873,-0.7699,.7699,-0.9041,.9041,-0.9816,.9816],m=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],n=0;for(var o=0;od;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function bx(){return this.hex}function bv(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bu(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bu(a,b){for(var c=0,d=a.length;c',bl=bk.firstChild,bl.style.behavior="url(#default#VML)";if(!bl||typeof bl.adj!="object")return a.type=p;bk=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};var bn=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("raphael.setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bo=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bo=bv(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bo=bv(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bo(b)},bp=function(){return"hsb("+[this.h,this.s,this.b]+")"},bq=function(){return"hsl("+[this.h,this.s,this.l]+")"},br=function(){return this.hex},bs=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},bt=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:br};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=br;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return bt(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bp}},a.rgb2hsl=function(a,b,c){c=bs(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bq}},a._path2string=function(){return this.join(",").replace(Y,"$1")};var bw=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bv(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bx};!X[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bo(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](W),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bx},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bx}},a),a.hsb=bv(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bv(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bv(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=function(b){if(!b)return null;var c=bz(b);if(c.arr)return bJ(c.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];a.is(b,E)&&a.is(b[0],E)&&(e=bJ(b)),e.length||r(b).replace(Z,function(a,b,c){var f=[],g=b.toLowerCase();c.replace(_,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(e.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")e.push([b][n](f));else while(f.length>=d[g]){e.push([b][n](f.splice(0,d[g])));if(!d[g])break}}),e.toString=a._path2string,c.arr=bJ(e);return e},a.parseTransformString=bv(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=bJ(b)),d.length||r(b).replace($,function(a,b,c){var e=[],f=v.call(b);c.replace(_,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d});var bz=function(a){var b=bz.ps=bz.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[g](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])});return b[a]};a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},a.isBBoxIntersect=function(b,c){var d=a.isPointInsideBBox;return d(c,b.x,b.y)||d(c,b.x2,b.y)||d(c,b.x,b.y2)||d(c,b.x2,b.y2)||d(b,c.x,c.y)||d(b,c.x2,c.y)||d(b,c.x,c.y2)||d(b,c.x2,c.y2)||(b.xc.x||c.xb.x)&&(b.yc.y||c.yb.y)},a.pathIntersection=function(a,b){return bH(a,b)},a.pathIntersectionNumber=function(a,b){return bH(a,b,1)},a.isPointInsidePath=function(b,c,d){var e=a.pathBBox(b);return a.isPointInsideBBox(e,c,d)&&bH(b,[["M",c,d],["H",e.x2+10]],1)%2==1},a._removedFactory=function(a){return function(){eve("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var bI=a.pathBBox=function(a){var b=bz(a);if(b.bbox)return b.bbox;if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=bR(a);var c=0,d=0,e=[],f=[],g;for(var h=0,i=a.length;h1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=aF&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bO(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bP(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bP(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bR=a._path2curve=bv(function(a,b){var c=!b&&bz(a);if(!b&&c.curve)return bJ(c.curve);var d=bL(a),e=b&&bL(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bO[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bN(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bN(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bM(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bM(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bM(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bM(b.x,b.y,b.X,b.Y))}return a},i=function(a,b){if(a[b].length>7){a[b].shift();var c=a[b];while(c.length)a.splice(b++,0,["C"][n](c.splice(0,6)));a.splice(b,1),l=x(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],l=x(d.length,e&&e.length||0))};for(var k=0,l=x(d.length,e&&e.length||0);ke){if(c&&!l.start){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cs(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cu=ct(1),cv=ct(),cw=ct(0,1);a.getTotalLength=cu,a.getPointAtLength=cv,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cw(a,b).end;var d=cw(a,c,1);return b?cw(d,b).end:d},cl.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return cu(this.attrs.path)}},cl.getPointAtLength=function(a){if(this.type=="path")return cv(this.attrs.path,a)},cl.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var cx=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};cx.easeIn=cx["ease-in"]=cx["<"],cx.easeOut=cx["ease-out"]=cx[">"],cx.easeInOut=cx["ease-in-out"]=cx["<>"],cx["back-in"]=cx.backIn,cx["back-out"]=cx.backOut;var cy=[],cz=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cA=function(){var b=+(new Date),c=0;for(;c1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cE(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cE(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cy.length&&cz(cA)},cB=function(a){return a>255?255:a<0?0:a};cl.animateWith=function(b,c,d,e,f,g){var h=this;if(h.removed){g&&g.call(h);return h}var i=d instanceof cD?d:a.animation(d,e,f,g),j,k;cE(i,h,i.percents[0],null,h.attr());for(var l=0,m=cy.length;l.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael)
--------------------------------------------------------------------------------
/resource/js/upload.js:
--------------------------------------------------------------------------------
1 | function fileSelected()
2 | {
3 | var file = document.getElementById('fileToUpload').files[0];
4 | var fileSize = 0;
5 | if (file.size > 1024 * 1024)
6 | fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
7 | else
8 | fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
9 | document.getElementById('fileInfo').style.display = 'block';
10 | document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
11 | document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
12 | document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
13 | }
14 |
15 | function getParam(paramName)
16 | {
17 | keyValStrings = document.URL.split('#')[1].split(';');
18 | for(var i = 0; i < keyValStrings.length; i++){
19 | if(keyValStrings[i].split('=')[0] == paramName){
20 | return keyValStrings[i].split('=')[1];
21 | }
22 | }
23 | }
24 |
25 | function documentReady(){
26 | document.getElementById('roomName').setAttribute('value', getParam('room'));
27 | }
28 |
--------------------------------------------------------------------------------
/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | source venv/bin/activate
4 | export PORT=6000
5 | python main.py >> output.`date +"%y-%m-%d"`.log 2>&1
6 | deactivate
7 |
--------------------------------------------------------------------------------
/run_tests.sh:
--------------------------------------------------------------------------------
1 | pushd test
2 | nosetests
3 | popd
4 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.3.0
2 |
--------------------------------------------------------------------------------
/set_heroku_path.sh:
--------------------------------------------------------------------------------
1 | heroku config:set LD_LIBRARY_PATH=/app/vendor/ffmpeg/ffmpeg/lib:\$LD_LIBRARY_PATH
2 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
--------------------------------------------------------------------------------
/test/files/sample.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anandtrex/collabdraw/4de4cc14e344b2bf18d7c89846bdcecb92ee5125/test/files/sample.pdf
--------------------------------------------------------------------------------
/test/uploadprocessor_integ_test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import os
3 | import glob
4 |
5 | import config
6 | from org.collabdraw.tools.uploadprocessor import process_uploaded_file
7 | from org.collabdraw.dbclient.dbclientfactory import DbClientFactory
8 | from org.collabdraw.tools.tools import delete_files
9 |
10 |
11 | class UploadProcessorIntegTest(unittest.TestCase):
12 | def uploadprocessor_test(self):
13 | dir_path = os.path.abspath("./files")
14 | key = "nosetest:upload"
15 | process_uploaded_file(dir_path, "sample.pdf", key)
16 | output_files = os.listdir(dir_path)
17 | self.assertEquals(len(output_files), 11)
18 | self.assertIn("sample.pdf", output_files)
19 | for i in range(0, 10):
20 | self.assertIn(str(i + 1) + "_image.png", output_files)
21 |
22 | key = "info:%s:npages" % key
23 | db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
24 | self.assertEquals(int(db_client.get(key)), 10)
25 |
26 | # Cleanup
27 | db_client.delete(key)
28 | delete_files(dir_path + "/*.png")
--------------------------------------------------------------------------------
/test/videomaker_integ_test.py:
--------------------------------------------------------------------------------
1 | __author__ = 'anand'
2 |
3 | import unittest
4 | import glob
5 | import os
6 |
7 | import config
8 | from org.collabdraw.dbclient.dbclientfactory import DbClientFactory
9 | from org.collabdraw.tools.videomaker import make_video
10 | from org.collabdraw.tools.tools import delete_files
11 |
12 | class VideoMakerIntegTest(unittest.TestCase):
13 | def videomaker_test(self):
14 | # Make sure tmp directory is clean to start with
15 | delete_files("tmp/*.mp4")
16 |
17 | key = "nosetest:video"
18 | path = [{'lineColor': 'black', 'y': 397, 'oldx': 813, 'oldy': 397, 'lineWidth': '3px', 'type': 'touchmove', 'x': 811},
19 | {'lineColor': 'black', 'y': 398, 'oldx': 811, 'oldy': 397, 'lineWidth': '3px', 'type': 'touchmove', 'x': 809},
20 | {'lineColor': 'black', 'y': 398, 'oldx': 809, 'oldy': 398, 'lineWidth': '3px', 'type': 'touchend', 'x': 809},
21 | {'lineColor': 'black', 'y': 399, 'oldx': 809, 'oldy': 398, 'lineWidth': '3px', 'type': 'touchend', 'x': 900},
22 | {'lineColor': 'black', 'y': 400, 'oldx': 900, 'oldy': 399, 'lineWidth': '3px', 'type': 'touchend', 'x': 910},
23 | {'lineColor': 'black', 'y': 401, 'oldx': 910, 'oldy': 400, 'lineWidth': '3px', 'type': 'touchend', 'x': 920},
24 | {'lineColor': 'black', 'y': 402, 'oldx': 920, 'oldy': 401, 'lineWidth': '3px', 'type': 'touchend', 'x': 930},
25 | {'lineColor': 'black', 'y': 403, 'oldx': 930, 'oldy': 402, 'lineWidth': '3px', 'type': 'touchend', 'x': 940},
26 | {'lineColor': 'black', 'y': 404, 'oldx': 940, 'oldy': 403, 'lineWidth': '3px', 'type': 'touchend', 'x': 950},
27 | {'lineColor': 'black', 'y': 500, 'oldx': 950, 'oldy': 404, 'lineWidth': '3px', 'type': 'touchend', 'x': 960},
28 | {'lineColor': 'black', 'y': 508, 'oldx': 960, 'oldy': 500, 'lineWidth': '3px', 'type': 'touchend', 'x': 1000},
29 | {'lineColor': 'black', 'y': 511, 'oldx': 1000, 'oldy': 508, 'lineWidth': '3px', 'type': 'touchend', 'x': 900},
30 | ]
31 | db_client = DbClientFactory.getDbClient(config.DB_CLIENT_TYPE)
32 | db_client.set(key, path)
33 | make_video(key)
34 |
35 | # Validation
36 | files = glob.glob("tmp/*")
37 | self.assertEquals(len(files), 1, "Expecting exactly one file in tmp directory")
38 | self.assertIn("mp4", files[0], "Expecting an video file")
39 |
40 | # Cleanup
41 | db_client.delete(key)
42 | delete_files("tmp/*.mp4")
43 | os.rmdir("tmp")
--------------------------------------------------------------------------------