├── .hgignore ├── LICENSE ├── README.rst ├── __init__.py ├── conf └── supervisord.conf ├── core ├── __init__.py ├── admin.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── runserver_socketio.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto__add_field_drawing_slug.py │ ├── 0003_auto__chg_field_drawing_data.py │ ├── 0004_auto__chg_field_drawing_data.py │ ├── 0005_auto__add_field_drawing_rating_votes__add_field_drawing_rating_score.py │ ├── 0006_auto__del_field_drawing_data.py │ ├── 0007_auto__chg_field_drawing_title.py │ └── __init__.py ├── models.py ├── templatetags │ ├── __init__.py │ └── core_tags.py ├── urls.py ├── utils.py └── views.py ├── fabfile.py ├── local_settings.py.template ├── manage.py ├── requirements.txt ├── settings.py ├── static ├── css │ ├── chosen.css │ ├── colorpicker.css │ ├── drawnby.css │ └── ui-lightness │ │ ├── images │ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png │ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png │ │ ├── ui-bg_flat_10_000000_40x100.png │ │ ├── ui-bg_glass_100_f6f6f6_1x400.png │ │ ├── ui-bg_glass_100_fdf5ce_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png │ │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png │ │ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_228ef1_256x240.png │ │ ├── ui-icons_ef8c08_256x240.png │ │ ├── ui-icons_ffd27a_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ │ └── jquery-ui-1.8.14.custom.css ├── img │ ├── bg-watermark.png │ ├── bg.png │ ├── chosen-sprite.png │ ├── colorpicker │ │ ├── blank.gif │ │ ├── colorpicker_background.png │ │ ├── colorpicker_hex.png │ │ ├── colorpicker_hsb_b.png │ │ ├── colorpicker_hsb_h.png │ │ ├── colorpicker_hsb_s.png │ │ ├── colorpicker_indic.gif │ │ ├── colorpicker_overlay.png │ │ ├── colorpicker_rgb_b.png │ │ ├── colorpicker_rgb_g.png │ │ ├── colorpicker_rgb_r.png │ │ ├── colorpicker_select.gif │ │ ├── colorpicker_submit.png │ │ ├── custom_background.png │ │ ├── custom_hex.png │ │ ├── custom_hsb_b.png │ │ ├── custom_hsb_h.png │ │ ├── custom_hsb_s.png │ │ ├── custom_indic.gif │ │ ├── custom_rgb_b.png │ │ ├── custom_rgb_g.png │ │ ├── custom_rgb_r.png │ │ ├── custom_submit.png │ │ ├── select (copy).png │ │ ├── select.png │ │ ├── select2.png │ │ └── slider.png │ ├── cursor_canvas.png │ ├── img-home-dblogo.png │ ├── img-home-explain.png │ ├── img-home-fb.png │ ├── img-home-twit.png │ ├── logo.png │ ├── palette.png │ ├── sp-functions.png │ └── sp-tools.png └── js │ ├── chosen.jquery.js │ ├── colorpicker.js │ ├── drawnby.js │ ├── excanvas.js │ ├── global.js │ ├── jquery-ui-1.8.14.custom.min.js │ ├── jquery.js │ ├── jquery.tmpl.js │ ├── jquery.tools.js │ ├── json.js │ └── socket.io.js ├── templates ├── about.html ├── base.html ├── drawing_footer.html ├── edit.html ├── error.html ├── index.html ├── list.html ├── login.html ├── rating.html └── view.html └── urls.py /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *.pyc 3 | *.pyo 4 | *.db 5 | *.rdb 6 | .DS_Store 7 | .coverage 8 | local_settings.py 9 | static/cache/* 10 | static/photos/* 11 | static/drawings/* 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Stephen McDonald, Josh de Blank and Travis White. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | `DrawnBy`_ is a collaborative drawing app built by `Josh de Blank`_, 2 | `Travis White`_ and `Stephen McDonald`_ for the 2011 `Django Dash`_. 3 | Users are able to create new drawings which anyone can contribute to 4 | in real-time. Drawings can then be saved to the DrawnBy gallery where 5 | users can view and vote on their favourite drawings. 6 | 7 | Technical Overview 8 | ------------------ 9 | 10 | Aside from Django, DrawnBy brings together several unique technologies. 11 | The `HTML5 canvas API`_ is used to implement client-side drawing features. 12 | These interactions are then sent over the wire using `websockets`_ 13 | via `Socket.IO`_. `gevent`_ is used on the server to maintain long running 14 | requests, and the key-value server `Redis`_ is used to store all drawing 15 | interactions. 16 | 17 | Installation 18 | ------------ 19 | 20 | DrawnBy was built on `Ubuntu 10.04`_ VPS provided by `Linode`_. The 21 | following packages are required and can be installed via apt:: 22 | 23 | sudo apt-get update 24 | sudo apt-get install build-essential 25 | sudo apt-get install python-pip 26 | sudo apt-get install python-imaging 27 | sudo apt-get install libevent-dev 28 | 29 | Redis can be downloaded, built and run with the following commands:: 30 | 31 | wget http://redis.googlecode.com/files/redis-2.2.12.tar.gz 32 | tar -xf redis-2.2.12.tar.gz 33 | cd redis-2.2.12 34 | sudo make 35 | ./src/redis-server 36 | 37 | Development of DrawnBy is managed using the `Mercurial`_ version control 38 | system and hosted on `BitBucket`_. With Mercurial installed, clone the 39 | repository with the following command:: 40 | 41 | hg clone http://bitbucket.org/stephenmcd/drawnby 42 | 43 | The required Python packages can then be installed via `pip`_ with the 44 | following command from the newly created ``drawnby`` project directory:: 45 | 46 | cd drawnby 47 | sudo pip install -r requirements.txt 48 | 49 | A database is then required. By default DrawnBy is configured for a SQLite 50 | database. Consult the `django documentation`_ for configuring other 51 | database servers. Once configured, the database can be created running the 52 | following commands:: 53 | 54 | python manage.py syncdb 55 | python manage.py migrate 56 | 57 | To handle websockets, DrawnBy requires a custom server based on gevent. 58 | As such a management command is provided to run the project:: 59 | 60 | sudo python manage.py runserver_socketio 61 | 62 | DrawnBy uses Twitter or Facebook to handle authentication. As such API 63 | keys are required for an app with eithe provider. Once API keys are 64 | obtained, rename the ``local_settings.py.template`` file in the ``drawnby`` 65 | project directory to ``local_settings.py`` and edit this file to set the 66 | keys where defined. 67 | 68 | .. _`DrawnBy`: http://drawnby.jupo.org/ 69 | .. _`Django Dash`: http://djangodash.com/ 70 | .. _`HTML5 canvas API`: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html 71 | .. _`websockets`: http://dev.w3.org/html5/websockets/ 72 | .. _`Socket.IO`: http://socket.io/ 73 | .. _`gevent`: http://www.gevent.org/ 74 | .. _`Redis`: http://redis.io/ 75 | .. _`Linode`: http://www.linode.com/ 76 | .. _`Ubuntu 10.04`: http://www.ubuntu.com/ 77 | .. _`Mercurial`: http://mercurial.selenic.com/ 78 | .. _`BitBucket`: https://bitbucket.org/ 79 | .. _`pip`: http://www.pip-installer.org/ 80 | .. _`django documentation`: https://docs.djangoproject.com/en/1.3/ref/databases/ 81 | .. _`Josh de Blank`: http://joshdeblank.com/ 82 | .. _`Travis White`: http://www.traviswhite.com.au/ 83 | .. _`Stephen McDonald`: http://steve.jupo.org/ 84 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/__init__.py -------------------------------------------------------------------------------- /conf/supervisord.conf: -------------------------------------------------------------------------------- 1 | [program:drawnby_app] 2 | command=python manage.py runserver_socketio 0.0.0.0:80 3 | directory=/home/dash/drawnby/ 4 | user=nobody 5 | autostart=true 6 | autorestart=true 7 | 8 | [program:drawnby_redis] 9 | command=redis-server 10 | user=nobody 11 | autostart=true 12 | autorestart=true 13 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/core/__init__.py -------------------------------------------------------------------------------- /core/admin.py: -------------------------------------------------------------------------------- 1 | 2 | from django.contrib import admin 3 | 4 | from core.models import Drawing 5 | 6 | 7 | admin.site.register(Drawing) 8 | -------------------------------------------------------------------------------- /core/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/core/management/__init__.py -------------------------------------------------------------------------------- /core/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/core/management/commands/__init__.py -------------------------------------------------------------------------------- /core/management/commands/runserver_socketio.py: -------------------------------------------------------------------------------- 1 | 2 | from re import match 3 | from thread import start_new_thread 4 | from time import sleep 5 | 6 | from django.core.management.base import BaseCommand, CommandError 7 | from django.core.management.commands.runserver import naiveip_re 8 | from socketio import socketio_manage 9 | from socketio.server import SocketIOServer 10 | from socketio.mixins import BroadcastMixin 11 | from socketio.namespace import BaseNamespace 12 | 13 | from core.utils import Actions 14 | 15 | 16 | class DrawingNamespace(BaseNamespace, BroadcastMixin): 17 | 18 | def __init__(self, environ, ns_name, request=None): 19 | super(DrawingNamespace, self).__init__(environ, ns_name, request) 20 | self.actions = Actions(self) 21 | 22 | def on_message(self, message): 23 | broadcast = self.actions([str(s) for s in message]) 24 | if broadcast: 25 | self.broadcast_event("message", message) 26 | 27 | 28 | def application(environ, start_response): 29 | socketio_manage(environ, {"": DrawingNamespace}) 30 | 31 | 32 | class Command(BaseCommand): 33 | 34 | def handle(self, addrport="", **kwargs): 35 | if not addrport: 36 | self.addr = "127.0.0.1" 37 | self.port = 9000 38 | else: 39 | m = match(naiveip_re, addrport) 40 | if m is None: 41 | raise CommandError('"%s" is not a valid port number ' 42 | 'or address:port pair.' % addrport) 43 | self.addr, _, _, _, self.port = m.groups() 44 | server = SocketIOServer((self.addr, int(self.port)), application) 45 | server.serve_forever() 46 | -------------------------------------------------------------------------------- /core/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'Drawing' 12 | db.create_table('core_drawing', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('title', self.gf('django.db.models.fields.CharField')(max_length=50)), 15 | ('data', self.gf('django.db.models.fields.TextField')()), 16 | ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), 17 | )) 18 | db.send_create_signal('core', ['Drawing']) 19 | 20 | # Adding M2M table for field users on 'Drawing' 21 | db.create_table('core_drawing_users', ( 22 | ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), 23 | ('drawing', models.ForeignKey(orm['core.drawing'], null=False)), 24 | ('user', models.ForeignKey(orm['auth.user'], null=False)) 25 | )) 26 | db.create_unique('core_drawing_users', ['drawing_id', 'user_id']) 27 | 28 | 29 | def backwards(self, orm): 30 | 31 | # Deleting model 'Drawing' 32 | db.delete_table('core_drawing') 33 | 34 | # Removing M2M table for field users on 'Drawing' 35 | db.delete_table('core_drawing_users') 36 | 37 | 38 | models = { 39 | 'auth.group': { 40 | 'Meta': {'object_name': 'Group'}, 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 43 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 44 | }, 45 | 'auth.permission': { 46 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 47 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 48 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 49 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 50 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 51 | }, 52 | 'auth.user': { 53 | 'Meta': {'object_name': 'User'}, 54 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 55 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 56 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 57 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 58 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 60 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 61 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 62 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 63 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 64 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 65 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 66 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 67 | }, 68 | 'contenttypes.contenttype': { 69 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 70 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 71 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 73 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 74 | }, 75 | 'core.drawing': { 76 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 77 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 78 | 'data': ('django.db.models.fields.TextField', [], {}), 79 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 80 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 81 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 82 | } 83 | } 84 | 85 | complete_apps = ['core'] 86 | -------------------------------------------------------------------------------- /core/migrations/0002_auto__add_field_drawing_slug.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding field 'Drawing.slug' 12 | db.add_column('core_drawing', 'slug', self.gf('django.db.models.fields.SlugField')(db_index=True, max_length=50, null=True, blank=True), keep_default=False) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Deleting field 'Drawing.slug' 18 | db.delete_column('core_drawing', 'slug') 19 | 20 | 21 | models = { 22 | 'auth.group': { 23 | 'Meta': {'object_name': 'Group'}, 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 27 | }, 28 | 'auth.permission': { 29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 34 | }, 35 | 'auth.user': { 36 | 'Meta': {'object_name': 'User'}, 37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 38 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 50 | }, 51 | 'contenttypes.contenttype': { 52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 57 | }, 58 | 'core.drawing': { 59 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 60 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 61 | 'data': ('django.db.models.fields.TextField', [], {}), 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), 64 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 65 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 66 | } 67 | } 68 | 69 | complete_apps = ['core'] 70 | -------------------------------------------------------------------------------- /core/migrations/0003_auto__chg_field_drawing_data.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Changing field 'Drawing.data' 12 | db.alter_column('core_drawing', 'data', self.gf('django.db.models.fields.TextField')(null=True)) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'Drawing.data' 18 | db.alter_column('core_drawing', 'data', self.gf('django.db.models.fields.TextField')(default='')) 19 | 20 | 21 | models = { 22 | 'auth.group': { 23 | 'Meta': {'object_name': 'Group'}, 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 27 | }, 28 | 'auth.permission': { 29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 34 | }, 35 | 'auth.user': { 36 | 'Meta': {'object_name': 'User'}, 37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 38 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 50 | }, 51 | 'contenttypes.contenttype': { 52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 57 | }, 58 | 'core.drawing': { 59 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 60 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 61 | 'data': ('django.db.models.fields.TextField', [], {'null': 'True'}), 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), 64 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 65 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 66 | } 67 | } 68 | 69 | complete_apps = ['core'] 70 | -------------------------------------------------------------------------------- /core/migrations/0004_auto__chg_field_drawing_data.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Changing field 'Drawing.data' 12 | db.alter_column('core_drawing', 'data', self.gf('django.db.models.fields.TextField')(default='')) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'Drawing.data' 18 | db.alter_column('core_drawing', 'data', self.gf('django.db.models.fields.TextField')(null=True)) 19 | 20 | 21 | models = { 22 | 'auth.group': { 23 | 'Meta': {'object_name': 'Group'}, 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 27 | }, 28 | 'auth.permission': { 29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 34 | }, 35 | 'auth.user': { 36 | 'Meta': {'object_name': 'User'}, 37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 38 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 50 | }, 51 | 'contenttypes.contenttype': { 52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 57 | }, 58 | 'core.drawing': { 59 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 60 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 61 | 'data': ('django.db.models.fields.TextField', [], {}), 62 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 63 | 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), 64 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 65 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 66 | } 67 | } 68 | 69 | complete_apps = ['core'] 70 | -------------------------------------------------------------------------------- /core/migrations/0005_auto__add_field_drawing_rating_votes__add_field_drawing_rating_score.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding field 'Drawing.rating_votes' 12 | db.add_column('core_drawing', 'rating_votes', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True), keep_default=False) 13 | 14 | # Adding field 'Drawing.rating_score' 15 | db.add_column('core_drawing', 'rating_score', self.gf('django.db.models.fields.IntegerField')(default=0, blank=True), keep_default=False) 16 | 17 | 18 | def backwards(self, orm): 19 | 20 | # Deleting field 'Drawing.rating_votes' 21 | db.delete_column('core_drawing', 'rating_votes') 22 | 23 | # Deleting field 'Drawing.rating_score' 24 | db.delete_column('core_drawing', 'rating_score') 25 | 26 | 27 | models = { 28 | 'auth.group': { 29 | 'Meta': {'object_name': 'Group'}, 30 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 31 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 32 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 33 | }, 34 | 'auth.permission': { 35 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 36 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 37 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 38 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 40 | }, 41 | 'auth.user': { 42 | 'Meta': {'object_name': 'User'}, 43 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 44 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 45 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 46 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 47 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 48 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 49 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 50 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 51 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 52 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 53 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 54 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 55 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 56 | }, 57 | 'contenttypes.contenttype': { 58 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 59 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 60 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 61 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 62 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 63 | }, 64 | 'core.drawing': { 65 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 66 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 67 | 'data': ('django.db.models.fields.TextField', [], {}), 68 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 69 | 'rating_score': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}), 70 | 'rating_votes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}), 71 | 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), 72 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 73 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 74 | } 75 | } 76 | 77 | complete_apps = ['core'] 78 | -------------------------------------------------------------------------------- /core/migrations/0006_auto__del_field_drawing_data.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Deleting field 'Drawing.data' 12 | db.delete_column('core_drawing', 'data') 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Adding field 'Drawing.data' 18 | db.add_column('core_drawing', 'data', self.gf('django.db.models.fields.TextField')(default=''), keep_default=False) 19 | 20 | 21 | models = { 22 | 'auth.group': { 23 | 'Meta': {'object_name': 'Group'}, 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 27 | }, 28 | 'auth.permission': { 29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 34 | }, 35 | 'auth.user': { 36 | 'Meta': {'object_name': 'User'}, 37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 38 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 50 | }, 51 | 'contenttypes.contenttype': { 52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 57 | }, 58 | 'core.drawing': { 59 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 60 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 62 | 'rating_score': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}), 63 | 'rating_votes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}), 64 | 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), 65 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 66 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 67 | } 68 | } 69 | 70 | complete_apps = ['core'] 71 | -------------------------------------------------------------------------------- /core/migrations/0007_auto__chg_field_drawing_title.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Changing field 'Drawing.title' 12 | db.alter_column('core_drawing', 'title', self.gf('django.db.models.fields.CharField')(max_length=20)) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'Drawing.title' 18 | db.alter_column('core_drawing', 'title', self.gf('django.db.models.fields.CharField')(max_length=50)) 19 | 20 | 21 | models = { 22 | 'auth.group': { 23 | 'Meta': {'object_name': 'Group'}, 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) 27 | }, 28 | 'auth.permission': { 29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) 34 | }, 35 | 'auth.user': { 36 | 'Meta': {'object_name': 'User'}, 37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 38 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) 50 | }, 51 | 'contenttypes.contenttype': { 52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 57 | }, 58 | 'core.drawing': { 59 | 'Meta': {'ordering': "('-id',)", 'object_name': 'Drawing'}, 60 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 62 | 'rating_score': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}), 63 | 'rating_votes': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}), 64 | 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '50', 'null': 'True', 'blank': 'True'}), 65 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '20'}), 66 | 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) 67 | } 68 | } 69 | 70 | complete_apps = ['core'] 71 | -------------------------------------------------------------------------------- /core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/core/migrations/__init__.py -------------------------------------------------------------------------------- /core/models.py: -------------------------------------------------------------------------------- 1 | 2 | from os import mkdir 3 | from os.path import join, exists 4 | from urllib import urlretrieve 5 | 6 | from django.conf import settings 7 | from django.db import models 8 | from django.template.defaultfilters import slugify 9 | from djangoratings.fields import RatingField 10 | from social_auth.signals import socialauth_registered 11 | 12 | 13 | class Drawing(models.Model): 14 | 15 | users = models.ManyToManyField("auth.User") 16 | title = models.CharField(max_length=20) 17 | slug = models.SlugField(blank=True, null=True) 18 | created_at = models.DateTimeField(auto_now_add=True) 19 | rating = RatingField(range=5) 20 | 21 | class Meta: 22 | ordering = ("-id",) 23 | 24 | def __unicode__(self): 25 | return self.title 26 | 27 | @models.permalink 28 | def get_absolute_url(self): 29 | return ("view", (self.slug,)) 30 | 31 | def save(self, *args, **kwargs): 32 | if not self.slug: 33 | i = 0 34 | self.slug = slugify(self.title) 35 | while True: 36 | if i > 0: 37 | if i > 1: 38 | self.slug = self.slug.rsplit("-", 1)[0] 39 | self.slug = "%s-%s" % (self.slug, i) 40 | try: 41 | Drawing.objects.exclude(id=self.id).get(slug=self.slug) 42 | except Drawing.DoesNotExist: 43 | break 44 | i += 1 45 | super(Drawing, self).save(*args, **kwargs) 46 | 47 | def create_profile(sender, user, response, details, **kwargs): 48 | try: 49 | # twitter 50 | photo_url = response["profile_image_url"] 51 | photo_url = "_reasonably_small".join(photo_url.rsplit("_normal", 1)) 52 | except KeyError: 53 | # facebook 54 | photo_url = "http://graph.facebook.com/%s/picture?type=large" % response["id"] 55 | path = join(settings.MEDIA_ROOT, "photos") 56 | if not exists(path): 57 | mkdir(path) 58 | urlretrieve(photo_url, join(path, str(user.id))) 59 | socialauth_registered.connect(create_profile, sender=None) 60 | -------------------------------------------------------------------------------- /core/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/core/templatetags/__init__.py -------------------------------------------------------------------------------- /core/templatetags/core_tags.py: -------------------------------------------------------------------------------- 1 | 2 | from django.template import Library 3 | from redis import ConnectionError 4 | 5 | from core.models import Drawing 6 | from core.utils import redis 7 | 8 | 9 | register = Library() 10 | 11 | @register.filter 12 | def photo_for_user(user): 13 | """ 14 | Just returns the path to the user's photo since we can't 15 | combined values in a template to pass to the thumbnail tag. 16 | """ 17 | return "photos/%s" % user.id 18 | 19 | @register.filter 20 | def image_for_drawing(drawing): 21 | """ 22 | Just returns the path to the drawing's image since we can't 23 | combined values in a template to pass to the thumbnail tag. 24 | """ 25 | return "drawings/%s" % drawing.id 26 | 27 | @register.simple_tag(takes_context=True) 28 | def load_in_progress(context): 29 | """ 30 | Returns the keys and numbers of users for each of the drawings 31 | in progress. 32 | """ 33 | progress = [] 34 | try: 35 | for key in redis.keys("users-*"): 36 | progress.append({ 37 | "key": key.split("-")[1], 38 | "users": [u.split(",")[0] for u in redis.smembers(key)], 39 | }) 40 | except ConnectionError: 41 | pass 42 | context["progress"] = sorted(progress, key=lambda x: len(x["users"]), reverse=True) 43 | return "" 44 | -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf.urls.defaults import * 3 | from django.conf import settings 4 | from django.views.generic.simple import direct_to_template 5 | 6 | 7 | urlpatterns = patterns("core.views", 8 | url("^new/$", "drawing_new", name="new"), 9 | url("^edit/(?P.*)/$", "drawing_edit", name="edit"), 10 | url("^all/$", "drawing_list", name="list"), 11 | url("^view/(?P.*)/$", "drawing_view", name="view"), 12 | url("^socket\.io", "socketio", name="socketio"), 13 | url("^rate/(?P\d+)/(?P\d+)/", "drawing_rate", name="rate"), 14 | url("^about/$", "about", name="about"), 15 | url("^auth/login/$", "login", name="login"), 16 | url("^auth/logout/$", "logout", name="logout"), 17 | url("^auth/error/$", direct_to_template, {"template": "error.html"}, name="error"), 18 | url("^%s/$" % settings.LOGIN_REDIRECT_URL.strip("/"), "loggedin", name="loggedin"), 19 | url("^$", direct_to_template, {"template": "index.html"}, name="home"), 20 | ) 21 | -------------------------------------------------------------------------------- /core/utils.py: -------------------------------------------------------------------------------- 1 | 2 | from os import mkdir 3 | from os.path import join, exists 4 | 5 | from django.conf import settings 6 | from django.contrib.auth.models import User 7 | from redis import Redis, ConnectionPool 8 | 9 | from core.models import Drawing 10 | 11 | 12 | redis = Redis(connection_pool=ConnectionPool()) 13 | 14 | 15 | class Actions(object): 16 | """ 17 | Callable object created with each socketio handler and handles 18 | each drawing action. 19 | """ 20 | 21 | def __init__(self, namespace): 22 | self.namespace = namespace 23 | self.handlers = ("join", "leave", "save", "mousedown") 24 | 25 | def __call__(self, message): 26 | """ 27 | Main entry point for handling an action. Determine the action 28 | and relevant redis keys from the message, and call the 29 | appropriate action handler. Each handler returns a bool used 30 | by the socketio view to determine whether to broadcast the 31 | message received. 32 | """ 33 | self.drawing_key, action = message[:2] 34 | self.drawing_data_key = "drawing-%s" % self.drawing_key 35 | self.users_data_key = "users-%s" % self.drawing_key 36 | self.drawers_data_key = "drawers-%s" % self.drawing_key 37 | if action in self.handlers: 38 | handler = getattr(self, action) 39 | else: 40 | handler = self.draw 41 | return handler(message) 42 | 43 | def join(self, message): 44 | """ 45 | User is joining - add them to the user set and send them all 46 | drawing actions and users as join actions. 47 | """ 48 | redis.sadd(self.users_data_key, ",".join(message[2:])) 49 | user_actions = [s for m in redis.smembers(self.users_data_key) 50 | for s in [self.drawing_key, "join"] + m.split(",")] 51 | drawing_actions = [s for m in redis.lrange(self.drawing_data_key, 0, -1) 52 | for s in m.split(",")] 53 | self.namespace.emit("message", user_actions + drawing_actions) 54 | return True 55 | 56 | def leave(self, message): 57 | """ 58 | User is leaving - remove them from the user set and remove 59 | the drawing action list and drawers set if there are no more 60 | users. 61 | """ 62 | redis.srem(self.users_data_key, ",".join(message[2:])) 63 | if len(redis.smembers(self.users_data_key)) == 0: 64 | redis.delete(self.drawing_data_key) 65 | redis.delete(self.drawers_data_key) 66 | return True 67 | 68 | def save(self, message): 69 | """ 70 | Create a drawing object given the title and image data. 71 | """ 72 | drawing = Drawing.objects.create(title=message[2][:20]) 73 | for user_id in redis.smembers(self.drawers_data_key): 74 | drawing.users.add(User.objects.get(id=user_id)) 75 | # Save image file for thumbnailing. 76 | path = join(settings.MEDIA_ROOT, "drawings") 77 | if not exists(path): 78 | mkdir(path) 79 | with open(join(path, str(drawing.id)), "wb") as f: 80 | f.write(message[3].split(",", 1)[1].replace(" ", "+").decode("base64")) 81 | return False 82 | 83 | def mousedown(self, message): 84 | """ 85 | Pull the `first` arg out of the messages, which is true the 86 | first time a user touches the drawing, add them to the set 87 | of actual drawers and pass the messages onto the draw handler. 88 | """ 89 | first = message.pop(-3) 90 | if str(first).lower() == "true": 91 | redis.sadd(self.drawers_data_key, message[-1]) 92 | return self.draw(message) 93 | 94 | def draw(self, message): 95 | """ 96 | Default action that handles any drawing events such as mouse 97 | movement and presses. Simply add the actions to the list for 98 | this drawing. 99 | """ 100 | redis.rpush(self.drawing_data_key, ",".join(message)) 101 | return True 102 | -------------------------------------------------------------------------------- /core/views.py: -------------------------------------------------------------------------------- 1 | 2 | from os.path import join 3 | from random import choice 4 | from string import letters, digits 5 | 6 | from django.conf import settings 7 | from django.contrib.auth.decorators import login_required 8 | from django.contrib.auth import logout as auth_logout 9 | from django.contrib import messages 10 | from django.http import HttpResponse 11 | from django.shortcuts import get_object_or_404, redirect, render 12 | from djangoratings.views import AddRatingFromModel 13 | import redis 14 | 15 | from core.models import Drawing 16 | from core.utils import Actions 17 | 18 | 19 | def socketio(request): 20 | """ 21 | Socket.IO handler. 22 | """ 23 | socket = request.environ["socketio"] 24 | actions = Actions(socket) 25 | try: 26 | while True: 27 | message = socket.recv() 28 | if len(message) > 0: 29 | broadcast = actions(message) 30 | if broadcast: 31 | socket.broadcast(message) 32 | else: 33 | if not socket.connected(): 34 | break 35 | except Exception, e: 36 | print e 37 | return HttpResponse("") 38 | 39 | @login_required 40 | def drawing_new(request): 41 | """ 42 | Creates a new drawing key and redirects to the edit view for it. 43 | """ 44 | drawing_key = "".join([choice(letters + digits) for i in range(6)]) 45 | return redirect("edit", drawing_key) 46 | 47 | @login_required 48 | def drawing_edit(request, drawing_key, template="edit.html"): 49 | """ 50 | Edit a drawing. 51 | """ 52 | context = {"drawing_key": drawing_key} 53 | return render(request, template, context) 54 | 55 | def drawing_list(request, template="list.html"): 56 | """ 57 | List all completed drawings. 58 | """ 59 | context = {"drawings": Drawing.objects.all()} 60 | return render(request, template, context) 61 | 62 | def drawing_view(request, slug, template="view.html"): 63 | """ 64 | Display a drawing. 65 | """ 66 | drawing = get_object_or_404(Drawing, slug=slug) 67 | context = {"drawing": drawing} 68 | return render(request, template, context) 69 | 70 | def drawing_rate(request, drawing_id, score): 71 | """ 72 | Wrap around djangorating's view so we can send a message and 73 | redirect back. 74 | """ 75 | view = AddRatingFromModel() 76 | response = view(request, "drawing", "core", drawing_id, "rating", score) 77 | messages.success(request, response.content) 78 | return redirect(request.GET.get("next", "list")) 79 | 80 | def login(request, template="login.html"): 81 | """ 82 | Login - store the next param as a cookie since session may be lost. 83 | """ 84 | context = {} 85 | response = render(request, template, context) 86 | response.set_cookie("next", request.GET.get("next", "home")) 87 | return response 88 | 89 | def loggedin(request): 90 | """ 91 | social-auth callback redirected to when user logs in. 92 | """ 93 | if request.user.is_authenticated(): 94 | messages.success(request, "Logged in") 95 | return redirect(request.COOKIES.get("next", "home")) 96 | 97 | def logout(request): 98 | """ 99 | Log out. 100 | """ 101 | if request.user.is_authenticated(): 102 | auth_logout(request) 103 | messages.success(request, "Logged out") 104 | return redirect("home") 105 | 106 | def about(request, template="about.html"): 107 | """ 108 | Convert the README file into HTML. 109 | """ 110 | from docutils.core import publish_string 111 | from docutils.writers.html4css1 import Writer, HTMLTranslator 112 | writer = Writer() 113 | writer.translator_class = HTMLTranslator 114 | with open(join(settings.PROJECT_ROOT, "README.rst"), "r") as f: 115 | about = publish_string(f.read(), writer=writer) 116 | with open(join(settings.PROJECT_ROOT, "LICENSE"), "r") as f: 117 | license = publish_string(f.read(), writer=writer) 118 | context = {"about": about, "license": license} 119 | return render(request, template, context) 120 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from fabric.api import * 5 | 6 | 7 | PROJECT_DIRNAME = "/home/dash/%s" % os.path.dirname(os.path.abspath( 8 | __file__)).split(os.sep)[-1] 9 | 10 | env.user = "dash" 11 | env.hosts = ["drawnby.jupo.org"] 12 | 13 | 14 | def push(): 15 | local("hg push") 16 | 17 | def pull(): 18 | with cd(PROJECT_DIRNAME): 19 | run("hg pull") 20 | run("hg up -C") 21 | 22 | def migrate(): 23 | with cd(PROJECT_DIRNAME): 24 | run("python manage.py syncdb") 25 | run("python manage.py migrate") 26 | 27 | def install(): 28 | with cd(PROJECT_DIRNAME): 29 | sudo("pip install -r requirements.txt") 30 | 31 | def restart(): 32 | with cd(PROJECT_DIRNAME): 33 | sudo("supervisorctl stop drawnby_app") 34 | sudo("supervisorctl start drawnby_app") 35 | 36 | def deploy(): 37 | push() 38 | pull() 39 | install() 40 | migrate() 41 | restart() 42 | -------------------------------------------------------------------------------- /local_settings.py.template: -------------------------------------------------------------------------------- 1 | 2 | TWITTER_CONSUMER_KEY = "" 3 | TWITTER_CONSUMER_SECRET = "" 4 | FACEBOOK_APP_ID = "" 5 | FACEBOOK_API_SECRET = "" 6 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from django.core.management import execute_manager 4 | try: 5 | import settings # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the " 9 | "directory containing %r. It appears you've customized things.\n" 10 | "You'll have to run django-admin.py, passing it your settings module.\n" 11 | "(If the file settings.py does indeed exist, it's causing an " 12 | "ImportError somehow.)\n" % __file__) 13 | sys.exit(1) 14 | 15 | if __name__ == "__main__": 16 | execute_manager(settings) 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.3 2 | django-social-auth==0.3.16 3 | South==0.7.3 4 | django-extensions==0.6 5 | django-compressor==0.9.2 6 | easy-thumbnails==1.0-alpha-17 7 | django-pagination==1.0.7 8 | gevent==0.13.7 9 | gevent-socketio==0.3.5-rc2 10 | gevent-websocket==0.3.6 11 | redis==2.4.6 12 | docutils==0.7 13 | django-ratings==0.3.6 14 | gunicorn==0.14.6 15 | PIL==1.1.7 16 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | 2 | TIME_ZONE = "Australia/Melbourne" 3 | TEMPLATE_DEBUG = True 4 | DEBUG = True 5 | SESSION_EXPIRE_AT_BROWSER_CLOSE = True 6 | SITE_ID = 1 7 | USE_I18N = False 8 | SECRET_KEY = "SRGF45gR45wtg$%G$RGt4tFTG%t6rsfbvsfdvBSR" 9 | INTERNAL_IPS = ("127.0.0.1", "60.241.76.198") 10 | 11 | TEMPLATE_LOADERS = ( 12 | "django.template.loaders.filesystem.Loader", 13 | "django.template.loaders.app_directories.Loader", 14 | ) 15 | 16 | DATABASES = { 17 | "default": { 18 | "ENGINE": "django.db.backends.sqlite3", 19 | "NAME": "drawnby.db", 20 | "USER": "", 21 | "PASSWORD": "", 22 | "HOST": "", 23 | "PORT": "", 24 | } 25 | } 26 | 27 | import os, sys 28 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) 29 | if PROJECT_ROOT not in sys.path: 30 | sys.path.insert(0, PROJECT_ROOT) 31 | full_path = lambda *parts: os.path.join(PROJECT_ROOT, *parts) 32 | 33 | PROJECT_DIRNAME = PROJECT_ROOT.split(os.sep)[-1] 34 | ADMIN_MEDIA_PREFIX = "/media/" 35 | MEDIA_URL = "/static/" 36 | MEDIA_ROOT = full_path(MEDIA_URL.strip("/")) 37 | ROOT_URLCONF = "urls" 38 | TEMPLATE_DIRS = full_path("templates") 39 | 40 | INSTALLED_APPS = ( 41 | 42 | # Django 43 | "django.contrib.admin", 44 | "django.contrib.auth", 45 | "django.contrib.contenttypes", 46 | "django.contrib.messages", 47 | "django.contrib.sessions", 48 | "django.contrib.sites", 49 | 50 | # 3rd Party 51 | "compressor", 52 | "django_extensions", 53 | "djangoratings", 54 | "easy_thumbnails", 55 | "pagination", 56 | "social_auth", 57 | "south", 58 | 59 | # Project 60 | "core", 61 | 62 | ) 63 | 64 | TEMPLATE_CONTEXT_PROCESSORS = ( 65 | "django.contrib.auth.context_processors.auth", 66 | "django.core.context_processors.debug", 67 | "django.core.context_processors.i18n", 68 | "django.core.context_processors.media", 69 | "django.core.context_processors.request", 70 | "django.contrib.messages.context_processors.messages", 71 | ) 72 | 73 | MIDDLEWARE_CLASSES = ( 74 | "django.contrib.sessions.middleware.SessionMiddleware", 75 | "django.contrib.auth.middleware.AuthenticationMiddleware", 76 | "django.middleware.common.CommonMiddleware", 77 | "django.middleware.csrf.CsrfViewMiddleware", 78 | "django.contrib.messages.middleware.MessageMiddleware", 79 | "pagination.middleware.PaginationMiddleware", 80 | ) 81 | 82 | MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage" 83 | 84 | AUTHENTICATION_BACKENDS = ( 85 | 'social_auth.backends.twitter.TwitterBackend', 86 | 'social_auth.backends.facebook.FacebookBackend', 87 | 'django.contrib.auth.backends.ModelBackend', 88 | ) 89 | 90 | # Set these in local_settings.py 91 | TWITTER_CONSUMER_KEY = "" 92 | TWITTER_CONSUMER_SECRET = "" 93 | FACEBOOK_APP_ID = "" 94 | FACEBOOK_API_SECRET = "" 95 | FACEBOOK_EXTENDED_PERMISSIONS = ["offline_access"] 96 | 97 | LOGIN_URL = '/auth/login/' 98 | LOGIN_REDIRECT_URL = '/auth/loggedin/' 99 | LOGIN_ERROR_URL = '/auth/error/' 100 | 101 | COMPRESS = True 102 | COMPRESS_OUTPUT_DIR = "cache" 103 | 104 | try: 105 | from local_settings import * 106 | except ImportError: 107 | pass 108 | -------------------------------------------------------------------------------- /static/css/chosen.css: -------------------------------------------------------------------------------- 1 | div.chzn-container { 2 | font-size: 13px; 3 | position: relative; 4 | vertical-align:middle; 5 | display:inline-block; 6 | margin-top:-2px; 7 | } 8 | 9 | div.chzn-container input { 10 | background: #fff; 11 | background: -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); 12 | background: -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); 13 | background: -o-linear-gradient(bottom, white 85%, #eeeeee 99%); 14 | background: -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); 15 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); 16 | background: linear-gradient(top, #ffffff 85%,#eeeeee 99%); 17 | border: 1px solid #aaa; 18 | font-family: sans-serif; 19 | font-size: 1em; 20 | margin: 0px; 21 | padding: 4px 20px 4px 5px; 22 | outline: none; 23 | -moz-border-radius: 3px; 24 | -webkit-border-radius: 3px; 25 | -o-border-radius: 3px; 26 | -ms-border-radius: 3px; 27 | -khtml-border-radius: 3px; 28 | border-radius: 3px; 29 | } 30 | div.chzn-container textarea:focus { 31 | border-color: #058cf5; 32 | -moz-box-shadow: 0px 0px 3px #aaa; 33 | -webkit-box-shadow: 0px 0px 3px #aaa; 34 | box-shadow: 0px 0px 3px #aaa; 35 | } 36 | 37 | 38 | div.chzn-container div.chzn-drop { 39 | background: #FFF; 40 | border: 1px solid #aaa; 41 | border-width: 0 1px 1px; 42 | left: 0; 43 | position: absolute; 44 | top: 29px; 45 | -webkit-box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.15); 46 | -moz-box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.15); 47 | box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15); 48 | z-index: 999; 49 | } 50 | div.chzn-container-single div.chzn-drop { 51 | -moz-border-radius: 0 0 4px 4px; 52 | -webkit-border-radius: 0 0 4px 4px; 53 | -o-border-radius: 0 0 4px 4px; 54 | -ms-border-radius: 0 0 4px 4px; 55 | -khtml-border-radius: 0 0 4px 4px; 56 | border-radius: 0 0 4px 4px; 57 | } 58 | 59 | 60 | /* SINGLE */ 61 | div.chzn-container a.chzn-single { 62 | background: #ffffff; 63 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white)); 64 | background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%); 65 | background-image: -o-linear-gradient(top, #eeeeee 0%,#ffffff 50%); 66 | background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 50%); 67 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 ); 68 | background-image: linear-gradient(top, #eeeeee 0%,#ffffff 50%); 69 | border: 1px solid #aaa; 70 | display: block; 71 | overflow: hidden; 72 | -moz-border-radius: 4px; 73 | -webkit-border-radius: 4px; 74 | -o-border-radius: 4px; 75 | -ms-border-radius: 4px; 76 | -khtml-border-radius: 4px; 77 | border-radius: 4px; 78 | height: 25px; 79 | color: #444; 80 | line-height: 26px; 81 | padding: 0px 0px 0px 8px; 82 | position: relative; 83 | text-decoration: none; 84 | white-space: nowrap; 85 | } 86 | div.chzn-container a.chzn-single span { 87 | display: block; 88 | margin-right: 26px; 89 | overflow: hidden; 90 | text-overflow: ellipsis; 91 | } 92 | div.chzn-container a.chzn-single div { 93 | -moz-border-radius-topright: 4px; 94 | -webkit-border-top-right-radius: 4px; 95 | -o-border-top-right-radius: 4px; 96 | -ms-border-top-right-radius: 4px; 97 | -khtml-border-top-right-radius: 4px; 98 | border-top-right-radius: 4px; 99 | -moz-border-radius-bottomright: 4px; 100 | -webkit-border-bottom-right-radius: 4px; 101 | -o-border-bottom-right-radius: 4px; 102 | -ms-border-bottom-right-radius: 4px; 103 | -khtml-border-bottom-right-radius: 4px; 104 | border-bottom-right-radius: 4px; 105 | background: #ccc; 106 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); 107 | background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); 108 | background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%); 109 | background-image: -ms-linear-gradient(top, #cccccc 0%,#eeeeee 60%); 110 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cccccc', endColorstr='#eeeeee',GradientType=0 ); 111 | background-image: linear-gradient(top, #cccccc 0%,#eeeeee 60%); 112 | border-left: 1px solid #aaa; 113 | display: block; 114 | height: 100%; 115 | position: absolute; 116 | right: 0; 117 | top: 0; 118 | width: 18px; 119 | } 120 | div.chzn-container a.chzn-single div b { 121 | background: url('../img/chosen-sprite.png') no-repeat 0 1px; 122 | display: block; 123 | width: 100%; 124 | height: 100%; 125 | } 126 | div.chzn-container div.chzn-search { 127 | padding: 3px 4px; 128 | margin: 0px; 129 | white-space: nowrap; 130 | } 131 | div.chzn-container div.chzn-search input { 132 | background: url('../img/chosen-sprite.png') no-repeat 100% -20px, #ffffff; 133 | background: url('../img/chosen-sprite.png') no-repeat 100% -20px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); 134 | background: url('../img/chosen-sprite.png') no-repeat 100% -20px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); 135 | background: url('../img/chosen-sprite.png') no-repeat 100% -20px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); 136 | background: url('../img/chosen-sprite.png') no-repeat 100% -20px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); 137 | background: url('../img/chosen-sprite.png') no-repeat 100% -20px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); 138 | background: linear-gradient(top, #ffffff 85%,#eeeeee 99%); 139 | -moz-border-radius: 0px; 140 | -webkit-border-radius: 0px; 141 | -o-border-radius: 0px; 142 | -ms-border-radius: 0px; 143 | -khtml-border-radius: 0px; 144 | border-radius: 0px; 145 | margin: 1px 0; 146 | outline: 0; 147 | } 148 | 149 | 150 | /* Multi */ 151 | div.chzn-container ul.chzn-choices { 152 | background: #fff; 153 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); 154 | background-image: -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); 155 | background-image: -o-linear-gradient(bottom, white 85%, #eeeeee 99%); 156 | background-image: -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%); 157 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); 158 | background-image: linear-gradient(top, #ffffff 85%,#eeeeee 99%); 159 | margin: 0; 160 | cursor: text; 161 | border: 1px solid #aaa; 162 | overflow: hidden; 163 | height: auto !important; 164 | height: 1%; 165 | padding: 0; 166 | position: relative; 167 | } 168 | div.chzn-container ul.chzn-choices:focus { 169 | border-color: #058cf5; 170 | -moz-box-shadow: 0px 0px 5px #999; 171 | -webkit-box-shadow: 0px 0px 5px #999; 172 | box-shadow: 0px 0px 5px #999; 173 | } 174 | div.chzn-container ul.chzn-choices li { 175 | float: left; 176 | list-style-type: none; 177 | margin: 0px; 178 | } 179 | div.chzn-container ul.chzn-choices li.search-field { 180 | margin: 0px; 181 | white-space: nowrap; 182 | padding: 0px; 183 | } 184 | div.chzn-container ul.chzn-choices li.search-field input { 185 | color: #666; 186 | background: transparent !important; 187 | border: 0px !important; 188 | padding: 5px; 189 | margin: 1px 0; 190 | outline: 0; 191 | -webkit-box-shadow: none; 192 | -moz-box-shadow: none; 193 | box-shadow: none; 194 | } 195 | div.chzn-container ul.chzn-choices li.search-field input.default { 196 | color: #999; 197 | } 198 | div.chzn-container ul.chzn-choices li.search-choice { 199 | -moz-border-radius: 3px; 200 | -webkit-border-radius: 3px; 201 | border-radius: 3px; 202 | background: #e4e4e4; 203 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #e4e4e4), color-stop(0.7, #eeeeee)); 204 | background-image: -moz-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%); 205 | background-image: -o-linear-gradient(bottom, #e4e4e4 0%, #eeeeee 70%); 206 | background: -ms-linear-gradient(top, #e4e4e4 0%,#eeeeee 70%); 207 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e4e4e4', endColorstr='#eeeeee',GradientType=0 ); 208 | background: linear-gradient(top, #e4e4e4 0%,#eeeeee 70%); 209 | color: #333; 210 | border: 1px solid #b4b4b4; 211 | line-height: 13px; 212 | padding: 3px 19px 3px 6px; 213 | position: relative; 214 | margin: 3px 0px 3px 5px; 215 | } 216 | div.chzn-container ul.chzn-choices li.search-choice span { 217 | cursor: default; 218 | } 219 | div.chzn-container ul.chzn-choices li.search-choice.search-choice-focus { 220 | background: #d4d4d4; 221 | } 222 | div.chzn-container ul.chzn-choices li.search-choice a.search-choice-close { 223 | position: absolute; 224 | right: 5px; 225 | top: 6px; 226 | display: block; 227 | width: 8px; 228 | height: 9px; 229 | font-size: 1px; 230 | background: url(../img/chosen-sprite.png) right top no-repeat; 231 | } 232 | div.chzn-container ul.chzn-choices li.search-choice a.search-choice-close:hover { 233 | background-position: right -9px; 234 | } 235 | div.chzn-container ul.chzn-choices li.search-choice.search-choice-focus a.search-choice-close { 236 | background-position: right -9px; 237 | } 238 | 239 | 240 | /* Results */ 241 | div.chzn-container ul.chzn-results { 242 | margin: 0 4px 4px 0; 243 | max-height: 190px; 244 | padding: 0 0 0 4px; 245 | position: relative; 246 | overflow-x: hidden; 247 | overflow-y: auto; 248 | } 249 | div.chzn-container-multi ul.chzn-results { 250 | margin: -1px 0 0; 251 | padding: 0; 252 | } 253 | div.chzn-container-multi ul.chzn-results li { 254 | border-left: 0px !important; 255 | border-right: 0px !important; 256 | } 257 | div.chzn-container ul.chzn-results li { 258 | line-height: 80%; 259 | padding: 7px 7px 8px; 260 | margin: 0; 261 | list-style-type: none; 262 | } 263 | div.chzn-container ul.chzn-results li.active-result { 264 | cursor: pointer; 265 | } 266 | div.chzn-container ul.chzn-results li em { 267 | font-style: normal; 268 | background: #FEFFDC; 269 | } 270 | div.chzn-container ul.chzn-results li.highlighted { 271 | background: #3875d7; 272 | color: #fff; 273 | } 274 | div.chzn-container ul.chzn-results li.highlighted em { 275 | background: transparent; 276 | } 277 | div.chzn-container ul.chzn-results li.no-results { 278 | background: #F4F4F4; 279 | } 280 | div.chzn-container ul.chzn-results li.group-result { 281 | cursor: default; 282 | color: #999; 283 | font-weight: bold; 284 | } 285 | div.chzn-container ul.chzn-results li.group-option { 286 | padding-left: 20px; 287 | } 288 | 289 | div.chzn-container-multi div.chzn-drop li.result-selected { 290 | display: none; 291 | } 292 | 293 | 294 | 295 | /* Active */ 296 | div.chzn-container-active a.chzn-single { 297 | -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); 298 | -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); 299 | box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); 300 | border: 1px solid #5897fb; 301 | } 302 | div.chzn-container-active a.chzn-single-with-drop { 303 | border: 1px solid #aaa; 304 | border-width: 1px 1px 1px; 305 | -moz-box-shadow: 0px 1px 0px #FFF inset; 306 | -webkit-box-shadow: 0px 1px 0px #FFF inset; 307 | box-shadow: 0px 1px 0px #FFF inset; 308 | background: #EEE; 309 | background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee)); 310 | background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%); 311 | background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%); 312 | background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%); 313 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); 314 | background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%); 315 | -webkit-border-bottom-left-radius: 0px; 316 | -webkit-border-bottom-right-radius: 0px; 317 | -moz-border-radius-bottomleft: 0px; 318 | -moz-border-radius-bottomright: 0px; 319 | border-bottom-left-radius: 0px; 320 | border-bottom-right-radius: 0px; 321 | } 322 | div.chzn-container-active a.chzn-single-with-drop div { 323 | background: transparent; 324 | border-left: none; 325 | } 326 | div.chzn-container-active a.chzn-single-with-drop div b { 327 | background-position: -18px 1px; 328 | } 329 | div.chzn-container-active ul.chzn-choices { 330 | -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); 331 | -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); 332 | box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); 333 | border: 1px solid #5897fb; 334 | } 335 | div.chzn-container-active ul.chzn-choices input { 336 | color: #111 !important; 337 | } 338 | -------------------------------------------------------------------------------- /static/css/colorpicker.css: -------------------------------------------------------------------------------- 1 | 2 | #color { 3 | position: relative; 4 | width: 30px; 5 | height: 30px; 6 | background: url(../img/colorpicker/select.png); 7 | } 8 | 9 | #color div { 10 | position: absolute; 11 | top: 3px; 12 | left: 3px; 13 | width: 24px; 14 | height: 24px; 15 | background: url(../img/colorpicker/select.png) center; 16 | } 17 | 18 | .colorpicker { 19 | width: 356px; 20 | height: 176px; 21 | overflow: hidden; 22 | position: absolute; 23 | background: url(../img/colorpicker/colorpicker_background.png); 24 | font-family: Arial, Helvetica, sans-serif; 25 | display: none; 26 | z-index:10; 27 | } 28 | .colorpicker_color { 29 | width: 150px; 30 | height: 150px; 31 | left: 14px; 32 | top: 13px; 33 | position: absolute; 34 | background: #f00; 35 | overflow: hidden; 36 | cursor: crosshair; 37 | } 38 | .colorpicker_color div { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | width: 150px; 43 | height: 150px; 44 | background: url(../img/colorpicker/colorpicker_overlay.png); 45 | } 46 | .colorpicker_color div div { 47 | position: absolute; 48 | top: 0; 49 | left: 0; 50 | width: 11px; 51 | height: 11px; 52 | overflow: hidden; 53 | background: url(../img/colorpicker/colorpicker_select.gif); 54 | margin: -5px 0 0 -5px; 55 | } 56 | .colorpicker_hue { 57 | position: absolute; 58 | top: 13px; 59 | left: 171px; 60 | width: 35px; 61 | height: 150px; 62 | cursor: n-resize; 63 | } 64 | .colorpicker_hue div { 65 | position: absolute; 66 | width: 35px; 67 | height: 9px; 68 | overflow: hidden; 69 | background: url(../img/colorpicker/colorpicker_indic.gif) left top; 70 | margin: -4px 0 0 0; 71 | left: 0px; 72 | } 73 | .colorpicker_new_color { 74 | position: absolute; 75 | width: 60px; 76 | height: 30px; 77 | left: 213px; 78 | top: 13px; 79 | background: #f00; 80 | } 81 | .colorpicker_current_color { 82 | position: absolute; 83 | width: 60px; 84 | height: 30px; 85 | left: 283px; 86 | top: 13px; 87 | background: #f00; 88 | } 89 | .colorpicker input { 90 | background-color: transparent; 91 | border: 1px solid transparent; 92 | position: absolute; 93 | font-size: 10px; 94 | font-family: Arial, Helvetica, sans-serif; 95 | color: #898989; 96 | top: 4px; 97 | right: 11px; 98 | text-align: right; 99 | margin: 0; 100 | padding: 0; 101 | height: 11px; 102 | } 103 | .colorpicker_hex { 104 | position: absolute; 105 | width: 72px; 106 | height: 22px; 107 | background: url(../img/colorpicker/colorpicker_hex.png) top; 108 | left: 212px; 109 | top: 142px; 110 | } 111 | .colorpicker_hex input { 112 | right: 6px; 113 | } 114 | .colorpicker_field { 115 | height: 22px; 116 | width: 62px; 117 | background-position: top; 118 | position: absolute; 119 | } 120 | .colorpicker_field span { 121 | position: absolute; 122 | width: 12px; 123 | height: 22px; 124 | overflow: hidden; 125 | top: 0; 126 | right: 0; 127 | cursor: n-resize; 128 | } 129 | .colorpicker_rgb_r { 130 | background-image: url(../img/colorpicker/colorpicker_rgb_r.png); 131 | top: 52px; 132 | left: 212px; 133 | } 134 | .colorpicker_rgb_g { 135 | background-image: url(../img/colorpicker/colorpicker_rgb_g.png); 136 | top: 82px; 137 | left: 212px; 138 | } 139 | .colorpicker_rgb_b { 140 | background-image: url(../img/colorpicker/colorpicker_rgb_b.png); 141 | top: 112px; 142 | left: 212px; 143 | } 144 | .colorpicker_hsb_h { 145 | background-image: url(../img/colorpicker/colorpicker_hsb_h.png); 146 | top: 52px; 147 | left: 282px; 148 | } 149 | .colorpicker_hsb_s { 150 | background-image: url(../img/colorpicker/colorpicker_hsb_s.png); 151 | top: 82px; 152 | left: 282px; 153 | } 154 | .colorpicker_hsb_b { 155 | background-image: url(../img/colorpicker/colorpicker_hsb_b.png); 156 | top: 112px; 157 | left: 282px; 158 | } 159 | .colorpicker_submit { 160 | position: absolute; 161 | width: 22px; 162 | height: 22px; 163 | background: url(../img/colorpicker/colorpicker_submit.png) top; 164 | left: 322px; 165 | top: 142px; 166 | overflow: hidden; 167 | } 168 | .colorpicker_focus { 169 | background-position: center; 170 | } 171 | .colorpicker_hex.colorpicker_focus { 172 | background-position: bottom; 173 | } 174 | .colorpicker_submit.colorpicker_focus { 175 | background-position: bottom; 176 | } 177 | .colorpicker_slider { 178 | background-position: bottom; 179 | } 180 | -------------------------------------------------------------------------------- /static/css/drawnby.css: -------------------------------------------------------------------------------- 1 | /* HTML5 ? Boilerplate */ 2 | 3 | html, body, div, span, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, 6 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, 7 | fieldset, form, label, legend, 8 | table, caption, tbody, tfoot, thead, tr, th, td, 9 | article, aside, canvas, details, figcaption, figure, 10 | footer, header, hgroup, menu, nav, section, summary, 11 | time, mark, audio, video { 12 | margin: 0; 13 | padding: 0; 14 | border: 0; 15 | font-size: 100%; 16 | font: inherit; 17 | vertical-align: baseline; 18 | } 19 | 20 | article, aside, details, figcaption, figure, 21 | footer, header, hgroup, menu, nav, section { 22 | display: block; 23 | } 24 | 25 | blockquote, q { quotes: none; } 26 | blockquote:before, blockquote:after, 27 | q:before, q:after { content: ""; content: none; } 28 | ins { background-color: #ff9; color: #000; text-decoration: none; } 29 | mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } 30 | del { text-decoration: line-through; } 31 | abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } 32 | table { border-collapse: collapse; border-spacing: 0; } 33 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 34 | input, select { vertical-align: middle; } 35 | 36 | body { font:13px/1.231 sans-serif; *font-size:small; } 37 | select, input, textarea, button { font:99% sans-serif; } 38 | pre, code, kbd, samp { font-family: monospace, sans-serif; } 39 | 40 | html { overflow-y: scroll; } 41 | a:hover, a:active { outline: none; } 42 | ul, ol { margin-left: 2em; } 43 | ol { list-style-type: decimal; } 44 | nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } 45 | small { font-size: 85%; } 46 | strong, th { font-weight: bold; } 47 | td { vertical-align: top; } 48 | sub, sup { font-size: 75%; line-height: 0; position: relative; } 49 | sup { top: -0.5em; } 50 | sub { bottom: -0.25em; } 51 | 52 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } 53 | textarea { overflow: auto; } 54 | .ie6 legend, .ie7 legend { margin-left: -7px; } 55 | input[type="radio"] { vertical-align: text-bottom; } 56 | input[type="checkbox"] { vertical-align: bottom; } 57 | .ie7 input[type="checkbox"] { vertical-align: baseline; } 58 | .ie6 input { vertical-align: text-bottom; } 59 | label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } 60 | button, input, select, textarea { margin: 0; } 61 | input:valid, textarea:valid { } 62 | input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } 63 | .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } 64 | 65 | 66 | ::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } 67 | ::selection { background:#FF5E99; color:#fff; text-shadow: none; } 68 | a:link { -webkit-tap-highlight-color: #FF5E99; } 69 | button { width: auto; overflow: visible; } 70 | .ie7 img { -ms-interpolation-mode: bicubic; } 71 | 72 | body, select, input, textarea { color: #444; } 73 | h1, h2, h3, h4, h5, h6 { font-weight: bold; } 74 | 75 | 76 | 77 | /** 78 | * Primary styles 79 | * 80 | * Author: @joshwins, @stephen_mcd 81 | */ 82 | 83 | /* Generic */ 84 | body { font-family: Arial, Helvetica, serif; background: url('../img/bg.png'); } 85 | a {color:#572B80;} 86 | .container { width: 960px; margin: 50px auto; background: #fff; -webkit-box-shadow: 0px 0px 60px 10px #ffffff; -moz-box-shadow: 0px 0px 60px 10px #ffffff; box-shadow: 0px 0px 60px 10px #ffffff; } 87 | header { border-bottom: 1px solid #fff; margin-bottom: 12px; background: #572b80; -webkit-box-shadow: 0px 5px 15px 5px #d0d0d0; -moz-box-shadow: 0px 5px 15px 5px #d0d0d0; box-shadow: 0px 5px 15px 5px #d0d0d0; } 88 | nav { float: right; width: 700px; text-align: right; padding-right:20px;} 89 | nav .button { display: inline-block; } 90 | nav .button a { display: block; margin: 28px 5px 0 0; padding: 5px 18px 8px; border: 1px solid #30134c; font-size: 12px; font-weight: bold; color: #fff; text-decoration: none; text-shadow: 0px 1px 2px #292929; 91 | -webkit-border-radius: 4px; 92 | -moz-border-radius: 4px; 93 | border-radius: 4px; 94 | background: -moz-linear-gradient(top, #9056c6 0%, #693996 100%); 95 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9056c6), color-stop(100%,#693996)); 96 | } 97 | nav .button a:hover, 98 | body.new #nav-new, 99 | body.gallery #nav-gallery, 100 | body.about #nav-about 101 | { background: #ed9519; background: -moz-linear-gradient(top, #ed9519 0%, #bb7109 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ed9519), color-stop(100%,#bb7109));} 102 | 103 | h1 { margin-left: 30px; font-size: 26px; text-transform: capitalize; } 104 | 105 | .message { position:absolute; z-index:1; text-align:center; background:#fff; display:none; color:#000; font-size:14px; top:0; width:100%; height:30px; padding-top:10px; } 106 | .message a { float:right; text-decoration:none; margin:-4px 10px 0 0; color:#000; font-size:20px; background:#eee; padding:0 8px 4px; } 107 | 108 | footer { margin:0 10px; padding:5px 10px 0 10px; color: #572b80; font-size: 11px; background: #efefef; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } 109 | footer img {border:1px solid #fff; vertical-align: middle; } 110 | footer a {color:#f99d1c; font-weight:bold;} 111 | footer span img {margin-right:5px;} 112 | footer .main {float:left; margin:5px;} 113 | footer .sharing {float:right; margin:5px 10px 0 0;} 114 | footer span.users, footer span.rate {margin:0 5px 0 20px;} 115 | 116 | /* home */ 117 | .intro { width: 800px; margin: 25px auto; font-size: 22px; color: #7c7c7c; line-height: 2; text-align: center; } 118 | .intro img { float: left; margin: 3px 0 0 40px; } 119 | .login-buttons { width: 320px; margin: 5px auto 20px; } 120 | .login-buttons img { margin: 0 5px; } 121 | .home footer {display:none;} 122 | .home #main {margin:0 !important;} 123 | 124 | /* About */ 125 | div.about {padding:0 20px;} 126 | div.about h1 {font-size:20px; margin:30px 0 10px 0;} 127 | 128 | /* Gallery */ 129 | .gallery-items div, .gallery-items img {width:152px;} 130 | .gallery-items div { 131 | float:left; text-align:center; border:1px solid #eee; margin:15px; 132 | -webkit-border-radius: 4px; 133 | -moz-border-radius: 4px; 134 | border-radius: 4px; 135 | -webkit-box-shadow: 0px 0px 15px #999999; 136 | -moz-box-shadow: 0px 0px 15px #999999; 137 | box-shadow: 0px 0px 15px #999999; 138 | } 139 | .gallery-items img {display:block;} 140 | 141 | /* Ratings */ 142 | .star-on, .star-off {text-decoration:none; font-size:18px !important; margin:0;} 143 | .star-on {color:#F99D1C;} 144 | .star-off {color:#ab97be;} 145 | 146 | .toolbar { float: left; width: 40px; margin: 0; background: #efefef; } 147 | .toolbar a { width: 30px; height: 30px; margin: 2px auto 3px auto; background: #cbcbcb url('../img/sp-tools.png') no-repeat 0 0; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; } 148 | .toolbar .select { background-position: 4px 5px; } 149 | .toolbar .pencil { background-position: 4px -31px; } 150 | .toolbar .brush { background-position: 4px -66px; cursor:default;} 151 | .toolbar .cut { background-position: 4px -101px; } 152 | .toolbar .eyedrop { background-position: 4px -136px; } 153 | .toolbar .zoom { background-position: 4px -170px; } 154 | .toolbar .save { background-position: 4px -206px; } 155 | .toolbar a:hover { background-color: #8a8a8a; } 156 | .toolbar .brush:hover {background-color: #cbcbcb !important;} 157 | .brushsize { height: 80px; margin: 15px; } 158 | .brushsize a {cursor:move !important;} 159 | #amount { position: relative; } 160 | #amount span { position: absolute; top: -16px; right: 7px; font-size: 10px; font-weight: bold; text-align: center; } 161 | 162 | .functions { display: inline-block; width: 200px; border-left: 2px solid #fff; text-align: center; } 163 | .functions a { display: inline-block; width: 20px; height: 20px; margin: 10px 2px 10px 3px; background: #cbcbcb url('../img/sp-functions.png') no-repeat 0 0; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; } 164 | .functions .save { background-position: 4px 3px; } 165 | .functions .print { background-position: -21px 3px; } 166 | .functions .email { background-position: -46px 3px; } 167 | .functions .undo { background-position: -71px 3px; } 168 | .functions .download { background-position: -96px 3px; } 169 | .functions .delete { background-position: -121px 4px; } 170 | .functions a:hover, 171 | .functions a.active { background-color: #8a8a8a; } 172 | 173 | .userslist { display: inline-block; width: 360px; height: 40px; margin-left: 10px; padding-left: 10px; border-left: 2px solid #fff; } 174 | .rating { display: inline-block; width: 150px; padding-left: 10px; border-left: 2px solid #fff; } 175 | 176 | #users, #users li {display:inline; margin:0;} 177 | #users .username {display:none;} 178 | span.users {margin-left:20px;} 179 | span.sep {margin:0 10px;} 180 | 181 | nav .progress {display:inline-block; text-align:left; padding-right:7px;} 182 | #progress {width:230px;} 183 | 184 | #canvas {border:1px solid #ccc; cursor: url('../img/cursor_canvas.png'), pointer; margin-left:20px;} 185 | 186 | .tooltip {padding:3px 6px; font-size:11px; margin-bottom:5px; 187 | -webkit-border-radius: 4px; 188 | -moz-border-radius: 4px; 189 | border-radius: 4px; 190 | -webkit-box-shadow: 1px 1px 3px #999999; 191 | -moz-box-shadow: 1px 1px 3px #999999; 192 | box-shadow: 1px 1px 3px #999999; 193 | background:#fff; border:1px solid #ccc; 194 | } 195 | 196 | .pagination { display: inline-block; width: 400px; text-align: center; } 197 | .pagination * {margin:0 4px;} 198 | 199 | #main {margin:20px; min-height:306px;} 200 | #loading {z-index:1; position:absolute; top:300px; left:0; width:100%;} 201 | .complete { 202 | border:1px solid #eee; margin:15px 30px; 203 | -webkit-border-radius: 4px; 204 | -moz-border-radius: 4px; 205 | border-radius: 4px; 206 | -webkit-box-shadow: 0px 0px 15px #999999; 207 | -moz-box-shadow: 0px 0px 15px #999999; 208 | box-shadow: 0px 0px 15px #999999; 209 | } 210 | 211 | /* Presets */ 212 | .ir { display: block; text-indent: -9999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } 213 | .hidden { display: none; visibility: hidden; } 214 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } 215 | .visuallyhidden.focusable:active, 216 | .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } 217 | .invisible { visibility: hidden; } 218 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 219 | .clearfix:after { clear: both; } 220 | .clearfix { zoom: 1; } 221 | 222 | 223 | @media all and (orientation:portrait) { 224 | 225 | } 226 | 227 | @media all and (orientation:landscape) { 228 | 229 | } 230 | 231 | @media screen and (max-device-width: 480px) { 232 | 233 | /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ 234 | } 235 | 236 | 237 | @media print { 238 | * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; 239 | -ms-filter: none !important; } 240 | a, a:visited { color: #444 !important; text-decoration: underline; } 241 | a[href]:after { content: " (" attr(href) ")"; } 242 | abbr[title]:after { content: " (" attr(title) ")"; } 243 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } 244 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 245 | thead { display: table-header-group; } 246 | tr, img { page-break-inside: avoid; } 247 | @page { margin: 0.5cm; } 248 | p, h2, h3 { orphans: 3; widows: 3; } 249 | h2, h3{ page-break-after: avoid; } 250 | } 251 | -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-icons_228ef1_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-icons_228ef1_256x240.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-icons_ef8c08_256x240.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-icons_ffd27a_256x240.png -------------------------------------------------------------------------------- /static/css/ui-lightness/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/css/ui-lightness/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /static/img/bg-watermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/bg-watermark.png -------------------------------------------------------------------------------- /static/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/bg.png -------------------------------------------------------------------------------- /static/img/chosen-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/chosen-sprite.png -------------------------------------------------------------------------------- /static/img/colorpicker/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/blank.gif -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_background.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_hex.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_hsb_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_hsb_b.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_hsb_h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_hsb_h.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_hsb_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_hsb_s.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_indic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_indic.gif -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_overlay.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_rgb_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_rgb_b.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_rgb_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_rgb_g.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_rgb_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_rgb_r.png -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_select.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_select.gif -------------------------------------------------------------------------------- /static/img/colorpicker/colorpicker_submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/colorpicker_submit.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_background.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_hex.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_hsb_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_hsb_b.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_hsb_h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_hsb_h.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_hsb_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_hsb_s.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_indic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_indic.gif -------------------------------------------------------------------------------- /static/img/colorpicker/custom_rgb_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_rgb_b.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_rgb_g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_rgb_g.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_rgb_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_rgb_r.png -------------------------------------------------------------------------------- /static/img/colorpicker/custom_submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/custom_submit.png -------------------------------------------------------------------------------- /static/img/colorpicker/select (copy).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/select (copy).png -------------------------------------------------------------------------------- /static/img/colorpicker/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/select.png -------------------------------------------------------------------------------- /static/img/colorpicker/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/select2.png -------------------------------------------------------------------------------- /static/img/colorpicker/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/colorpicker/slider.png -------------------------------------------------------------------------------- /static/img/cursor_canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/cursor_canvas.png -------------------------------------------------------------------------------- /static/img/img-home-dblogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/img-home-dblogo.png -------------------------------------------------------------------------------- /static/img/img-home-explain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/img-home-explain.png -------------------------------------------------------------------------------- /static/img/img-home-fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/img-home-fb.png -------------------------------------------------------------------------------- /static/img/img-home-twit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/img-home-twit.png -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/logo.png -------------------------------------------------------------------------------- /static/img/palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/palette.png -------------------------------------------------------------------------------- /static/img/sp-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/sp-functions.png -------------------------------------------------------------------------------- /static/img/sp-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephenmcd/drawnby/a3994e0743d2a18220c2efeb4918d9a13d93d6ba/static/img/sp-tools.png -------------------------------------------------------------------------------- /static/js/colorpicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Color picker 4 | * Author: Stefan Petre www.eyecon.ro 5 | * 6 | * Dual licensed under the MIT and GPL licenses 7 | * 8 | */ 9 | (function ($) { 10 | var ColorPicker = function () { 11 | var 12 | ids = {}, 13 | inAction, 14 | charMin = 65, 15 | visible, 16 | tpl = '
', 17 | defaults = { 18 | eventName: 'click', 19 | onShow: function () {}, 20 | onBeforeShow: function(){}, 21 | onHide: function () {}, 22 | onChange: function () {}, 23 | onSubmit: function () {}, 24 | color: 'ff0000', 25 | livePreview: true, 26 | flat: false 27 | }, 28 | fillRGBFields = function (hsb, cal) { 29 | var rgb = HSBToRGB(hsb); 30 | $(cal).data('colorpicker').fields 31 | .eq(1).val(rgb.r).end() 32 | .eq(2).val(rgb.g).end() 33 | .eq(3).val(rgb.b).end(); 34 | }, 35 | fillHSBFields = function (hsb, cal) { 36 | $(cal).data('colorpicker').fields 37 | .eq(4).val(hsb.h).end() 38 | .eq(5).val(hsb.s).end() 39 | .eq(6).val(hsb.b).end(); 40 | }, 41 | fillHexFields = function (hsb, cal) { 42 | $(cal).data('colorpicker').fields 43 | .eq(0).val(HSBToHex(hsb)).end(); 44 | }, 45 | setSelector = function (hsb, cal) { 46 | $(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100})); 47 | $(cal).data('colorpicker').selectorIndic.css({ 48 | left: parseInt(150 * hsb.s/100, 10), 49 | top: parseInt(150 * (100-hsb.b)/100, 10) 50 | }); 51 | }, 52 | setHue = function (hsb, cal) { 53 | $(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10)); 54 | }, 55 | setCurrentColor = function (hsb, cal) { 56 | $(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb)); 57 | }, 58 | setNewColor = function (hsb, cal) { 59 | $(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb)); 60 | }, 61 | keyDown = function (ev) { 62 | var pressedKey = ev.charCode || ev.keyCode || -1; 63 | if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) { 64 | return false; 65 | } 66 | var cal = $(this).parent().parent(); 67 | if (cal.data('colorpicker').livePreview === true) { 68 | change.apply(this); 69 | } 70 | }, 71 | change = function (ev) { 72 | var cal = $(this).parent().parent(), col; 73 | if (this.parentNode.className.indexOf('_hex') > 0) { 74 | cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value)); 75 | } else if (this.parentNode.className.indexOf('_hsb') > 0) { 76 | cal.data('colorpicker').color = col = fixHSB({ 77 | h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10), 78 | s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10), 79 | b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10) 80 | }); 81 | } else { 82 | cal.data('colorpicker').color = col = RGBToHSB(fixRGB({ 83 | r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10), 84 | g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10), 85 | b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10) 86 | })); 87 | } 88 | if (ev) { 89 | fillRGBFields(col, cal.get(0)); 90 | fillHexFields(col, cal.get(0)); 91 | fillHSBFields(col, cal.get(0)); 92 | } 93 | setSelector(col, cal.get(0)); 94 | setHue(col, cal.get(0)); 95 | setNewColor(col, cal.get(0)); 96 | cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]); 97 | }, 98 | blur = function (ev) { 99 | var cal = $(this).parent().parent(); 100 | cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus'); 101 | }, 102 | focus = function () { 103 | charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65; 104 | $(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus'); 105 | $(this).parent().addClass('colorpicker_focus'); 106 | }, 107 | downIncrement = function (ev) { 108 | var field = $(this).parent().find('input').focus(); 109 | var current = { 110 | el: $(this).parent().addClass('colorpicker_slider'), 111 | max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), 112 | y: ev.pageY, 113 | field: field, 114 | val: parseInt(field.val(), 10), 115 | preview: $(this).parent().parent().data('colorpicker').livePreview 116 | }; 117 | $(document).bind('mouseup', current, upIncrement); 118 | $(document).bind('mousemove', current, moveIncrement); 119 | }, 120 | moveIncrement = function (ev) { 121 | ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10)))); 122 | if (ev.data.preview) { 123 | change.apply(ev.data.field.get(0), [true]); 124 | } 125 | return false; 126 | }, 127 | upIncrement = function (ev) { 128 | change.apply(ev.data.field.get(0), [true]); 129 | ev.data.el.removeClass('colorpicker_slider').find('input').focus(); 130 | $(document).unbind('mouseup', upIncrement); 131 | $(document).unbind('mousemove', moveIncrement); 132 | return false; 133 | }, 134 | downHue = function (ev) { 135 | var current = { 136 | cal: $(this).parent(), 137 | y: $(this).offset().top 138 | }; 139 | current.preview = current.cal.data('colorpicker').livePreview; 140 | $(document).bind('mouseup', current, upHue); 141 | $(document).bind('mousemove', current, moveHue); 142 | }, 143 | moveHue = function (ev) { 144 | change.apply( 145 | ev.data.cal.data('colorpicker') 146 | .fields 147 | .eq(4) 148 | .val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10)) 149 | .get(0), 150 | [ev.data.preview] 151 | ); 152 | return false; 153 | }, 154 | upHue = function (ev) { 155 | fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); 156 | fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); 157 | $(document).unbind('mouseup', upHue); 158 | $(document).unbind('mousemove', moveHue); 159 | return false; 160 | }, 161 | downSelector = function (ev) { 162 | var current = { 163 | cal: $(this).parent(), 164 | pos: $(this).offset() 165 | }; 166 | current.preview = current.cal.data('colorpicker').livePreview; 167 | $(document).bind('mouseup', current, upSelector); 168 | $(document).bind('mousemove', current, moveSelector); 169 | }, 170 | moveSelector = function (ev) { 171 | change.apply( 172 | ev.data.cal.data('colorpicker') 173 | .fields 174 | .eq(6) 175 | .val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10)) 176 | .end() 177 | .eq(5) 178 | .val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10)) 179 | .get(0), 180 | [ev.data.preview] 181 | ); 182 | return false; 183 | }, 184 | upSelector = function (ev) { 185 | fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); 186 | fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0)); 187 | $(document).unbind('mouseup', upSelector); 188 | $(document).unbind('mousemove', moveSelector); 189 | return false; 190 | }, 191 | enterSubmit = function (ev) { 192 | $(this).addClass('colorpicker_focus'); 193 | }, 194 | leaveSubmit = function (ev) { 195 | $(this).removeClass('colorpicker_focus'); 196 | }, 197 | clickSubmit = function (ev) { 198 | var cal = $(this).parent(); 199 | var col = cal.data('colorpicker').color; 200 | cal.data('colorpicker').origColor = col; 201 | setCurrentColor(col, cal.get(0)); 202 | cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el); 203 | }, 204 | show = function (ev) { 205 | var cal = $('#' + $(this).data('colorpickerId')); 206 | cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]); 207 | var pos = $(this).offset(); 208 | var viewPort = getViewport(); 209 | var top = pos.top + this.offsetHeight; 210 | var left = pos.left; 211 | if (top + 176 > viewPort.t + viewPort.h) { 212 | top -= this.offsetHeight + 176; 213 | } 214 | if (left + 356 > viewPort.l + viewPort.w) { 215 | left -= 356; 216 | } 217 | cal.css({left: left + 'px', top: top + 'px'}); 218 | if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) { 219 | cal.show(); 220 | } 221 | $(document).bind('mousedown', {cal: cal}, hide); 222 | return false; 223 | }, 224 | hide = function (ev) { 225 | if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) { 226 | if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) { 227 | ev.data.cal.hide(); 228 | } 229 | $(document).unbind('mousedown', hide); 230 | } 231 | }, 232 | isChildOf = function(parentEl, el, container) { 233 | if (parentEl == el) { 234 | return true; 235 | } 236 | if (parentEl.contains) { 237 | return parentEl.contains(el); 238 | } 239 | if ( parentEl.compareDocumentPosition ) { 240 | return !!(parentEl.compareDocumentPosition(el) & 16); 241 | } 242 | var prEl = el.parentNode; 243 | while(prEl && prEl != container) { 244 | if (prEl == parentEl) 245 | return true; 246 | prEl = prEl.parentNode; 247 | } 248 | return false; 249 | }, 250 | getViewport = function () { 251 | var m = document.compatMode == 'CSS1Compat'; 252 | return { 253 | l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), 254 | t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop), 255 | w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth), 256 | h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight) 257 | }; 258 | }, 259 | fixHSB = function (hsb) { 260 | return { 261 | h: Math.min(360, Math.max(0, hsb.h)), 262 | s: Math.min(100, Math.max(0, hsb.s)), 263 | b: Math.min(100, Math.max(0, hsb.b)) 264 | }; 265 | }, 266 | fixRGB = function (rgb) { 267 | return { 268 | r: Math.min(255, Math.max(0, rgb.r)), 269 | g: Math.min(255, Math.max(0, rgb.g)), 270 | b: Math.min(255, Math.max(0, rgb.b)) 271 | }; 272 | }, 273 | fixHex = function (hex) { 274 | var len = 6 - hex.length; 275 | if (len > 0) { 276 | var o = []; 277 | for (var i=0; i -1) ? hex.substring(1) : hex), 16); 287 | return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; 288 | }, 289 | HexToHSB = function (hex) { 290 | return RGBToHSB(HexToRGB(hex)); 291 | }, 292 | RGBToHSB = function (rgb) { 293 | var hsb = { 294 | h: 0, 295 | s: 0, 296 | b: 0 297 | }; 298 | var min = Math.min(rgb.r, rgb.g, rgb.b); 299 | var max = Math.max(rgb.r, rgb.g, rgb.b); 300 | var delta = max - min; 301 | hsb.b = max; 302 | if (max != 0) { 303 | 304 | } 305 | hsb.s = max != 0 ? 255 * delta / max : 0; 306 | if (hsb.s != 0) { 307 | if (rgb.r == max) { 308 | hsb.h = (rgb.g - rgb.b) / delta; 309 | } else if (rgb.g == max) { 310 | hsb.h = 2 + (rgb.b - rgb.r) / delta; 311 | } else { 312 | hsb.h = 4 + (rgb.r - rgb.g) / delta; 313 | } 314 | } else { 315 | hsb.h = -1; 316 | } 317 | hsb.h *= 60; 318 | if (hsb.h < 0) { 319 | hsb.h += 360; 320 | } 321 | hsb.s *= 100/255; 322 | hsb.b *= 100/255; 323 | return hsb; 324 | }, 325 | HSBToRGB = function (hsb) { 326 | var rgb = {}; 327 | var h = Math.round(hsb.h); 328 | var s = Math.round(hsb.s*255/100); 329 | var v = Math.round(hsb.b*255/100); 330 | if(s == 0) { 331 | rgb.r = rgb.g = rgb.b = v; 332 | } else { 333 | var t1 = v; 334 | var t2 = (255-s)*v/255; 335 | var t3 = (t1-t2)*(h%60)/60; 336 | if(h==360) h = 0; 337 | if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} 338 | else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} 339 | else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} 340 | else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} 341 | else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} 342 | else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} 343 | else {rgb.r=0; rgb.g=0; rgb.b=0} 344 | } 345 | return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; 346 | }, 347 | RGBToHex = function (rgb) { 348 | var hex = [ 349 | rgb.r.toString(16), 350 | rgb.g.toString(16), 351 | rgb.b.toString(16) 352 | ]; 353 | $.each(hex, function (nr, val) { 354 | if (val.length == 1) { 355 | hex[nr] = '0' + val; 356 | } 357 | }); 358 | return hex.join(''); 359 | }, 360 | HSBToHex = function (hsb) { 361 | return RGBToHex(HSBToRGB(hsb)); 362 | }, 363 | restoreOriginal = function () { 364 | var cal = $(this).parent(); 365 | var col = cal.data('colorpicker').origColor; 366 | cal.data('colorpicker').color = col; 367 | fillRGBFields(col, cal.get(0)); 368 | fillHexFields(col, cal.get(0)); 369 | fillHSBFields(col, cal.get(0)); 370 | setSelector(col, cal.get(0)); 371 | setHue(col, cal.get(0)); 372 | setNewColor(col, cal.get(0)); 373 | }; 374 | return { 375 | init: function (opt) { 376 | opt = $.extend({}, defaults, opt||{}); 377 | if (typeof opt.color == 'string') { 378 | opt.color = HexToHSB(opt.color); 379 | } else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) { 380 | opt.color = RGBToHSB(opt.color); 381 | } else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) { 382 | opt.color = fixHSB(opt.color); 383 | } else { 384 | return this; 385 | } 386 | return this.each(function () { 387 | if (!$(this).data('colorpickerId')) { 388 | var options = $.extend({}, opt); 389 | options.origColor = opt.color; 390 | var id = 'collorpicker_' + parseInt(Math.random() * 1000); 391 | $(this).data('colorpickerId', id); 392 | var cal = $(tpl).attr('id', id); 393 | if (options.flat) { 394 | cal.appendTo(this).show(); 395 | } else { 396 | cal.appendTo(document.body); 397 | } 398 | options.fields = cal 399 | .find('input') 400 | .bind('keyup', keyDown) 401 | .bind('change', change) 402 | .bind('blur', blur) 403 | .bind('focus', focus); 404 | cal 405 | .find('span').bind('mousedown', downIncrement).end() 406 | .find('>div.colorpicker_current_color').bind('click', restoreOriginal); 407 | options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector); 408 | options.selectorIndic = options.selector.find('div div'); 409 | options.el = this; 410 | options.hue = cal.find('div.colorpicker_hue div'); 411 | cal.find('div.colorpicker_hue').bind('mousedown', downHue); 412 | options.newColor = cal.find('div.colorpicker_new_color'); 413 | options.currentColor = cal.find('div.colorpicker_current_color'); 414 | cal.data('colorpicker', options); 415 | cal.find('div.colorpicker_submit') 416 | .bind('mouseenter', enterSubmit) 417 | .bind('mouseleave', leaveSubmit) 418 | .bind('click', clickSubmit); 419 | fillRGBFields(options.color, cal.get(0)); 420 | fillHSBFields(options.color, cal.get(0)); 421 | fillHexFields(options.color, cal.get(0)); 422 | setHue(options.color, cal.get(0)); 423 | setSelector(options.color, cal.get(0)); 424 | setCurrentColor(options.color, cal.get(0)); 425 | setNewColor(options.color, cal.get(0)); 426 | if (options.flat) { 427 | cal.css({ 428 | position: 'relative', 429 | display: 'block' 430 | }); 431 | } else { 432 | $(this).bind(options.eventName, show); 433 | } 434 | } 435 | }); 436 | }, 437 | showPicker: function() { 438 | return this.each( function () { 439 | if ($(this).data('colorpickerId')) { 440 | show.apply(this); 441 | } 442 | }); 443 | }, 444 | hidePicker: function() { 445 | return this.each( function () { 446 | if ($(this).data('colorpickerId')) { 447 | $('#' + $(this).data('colorpickerId')).hide(); 448 | } 449 | }); 450 | }, 451 | setColor: function(col) { 452 | if (typeof col == 'string') { 453 | col = HexToHSB(col); 454 | } else if (col.r != undefined && col.g != undefined && col.b != undefined) { 455 | col = RGBToHSB(col); 456 | } else if (col.h != undefined && col.s != undefined && col.b != undefined) { 457 | col = fixHSB(col); 458 | } else { 459 | return this; 460 | } 461 | return this.each(function(){ 462 | if ($(this).data('colorpickerId')) { 463 | var cal = $('#' + $(this).data('colorpickerId')); 464 | cal.data('colorpicker').color = col; 465 | cal.data('colorpicker').origColor = col; 466 | fillRGBFields(col, cal.get(0)); 467 | fillHSBFields(col, cal.get(0)); 468 | fillHexFields(col, cal.get(0)); 469 | setHue(col, cal.get(0)); 470 | setSelector(col, cal.get(0)); 471 | setCurrentColor(col, cal.get(0)); 472 | setNewColor(col, cal.get(0)); 473 | } 474 | }); 475 | } 476 | }; 477 | }(); 478 | $.fn.extend({ 479 | ColorPicker: ColorPicker.init, 480 | ColorPickerHide: ColorPicker.hidePicker, 481 | ColorPickerShow: ColorPicker.showPicker, 482 | ColorPickerSetColor: ColorPicker.setColor 483 | }); 484 | })(jQuery) -------------------------------------------------------------------------------- /static/js/drawnby.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // Set up the canvas context. 4 | var canvas = $('#canvas'); 5 | var context = canvas.get()[0].getContext('2d'); 6 | context.width = canvas.width(); 7 | context.height = canvas.height(); 8 | context.lineCap = 'round'; 9 | context.lineJoin = 'round'; 10 | 11 | var drawing = false; // Mousedown/drawing flag. 12 | var first = true; // First touch of the canvas flag. 13 | var dirty = false; // Flag to prompt for saving on exit. 14 | var color = [0,0,0]; // Current drawing rgb. 15 | var size = 1; // Current drawing size. 16 | 17 | // Stores actions that come through when the user is drawing, 18 | // to be called when drawing is complete. 19 | var queue = []; 20 | 21 | // Container that stores each of the actions so that they can 22 | // be referenced dynamically by name. Triggered client-side by 23 | // the current user, or from the server via Socket.IO for all 24 | // other users for this drawing. 25 | var actions = { 26 | 27 | // Start drawing. 28 | mousedown: function(x, y, r, g, b, size, username, userID) { 29 | context.lineWidth = size; 30 | context.strokeStyle = 'rgba(' + [r, g, b, 1].join(',') + ')'; 31 | context.beginPath(); 32 | context.moveTo(x, y); 33 | }, 34 | 35 | // Draw. 36 | mousemove: function(x, y, r, g, b, size, username, userID) { 37 | context.lineWidth = size; 38 | context.strokeStyle = 'rgba(' + [r, g, b, 1].join(',') + ')'; 39 | context.lineTo(x, y); 40 | context.stroke(); 41 | }, 42 | 43 | load: function(imageData) { 44 | img = new Image(); 45 | var interval = setInterval(function() { 46 | if (img.complete) { 47 | context.drawImage(img, 0, 0); 48 | clearTimeout(interval); 49 | } 50 | }, 100); 51 | img.src = imageData; 52 | }, 53 | 54 | // User joining - add their name to the user list. 55 | join: function(username, userID) { 56 | if ($('#user-' + userID).length == 0) { 57 | messages.add(username + ' has joined'); 58 | data = {userID: userID, username: username}; 59 | $('#user-template').tmpl(data).appendTo('#users'); 60 | $('#user-' + userID + ' img').tooltip({offset: [-5, 0]}); 61 | } 62 | }, 63 | 64 | // User leaving - remove their name from the user list. 65 | leave: function(username, userID) { 66 | if (userID != window.userID) { 67 | messages.add(username + ' has left'); 68 | $('#user-' + userID).remove(); 69 | } 70 | } 71 | 72 | }; 73 | 74 | // Takes an array of args that represent one or more actions to call. 75 | // First arg is always the drawing key as a guard to ensure actions are 76 | // only performed for the current drawing. Second arg is the act‭ion name 77 | // and the rest are args for that action. Since many sets of actions 78 | // can be sent from the server in one batch, we keep looping and test 79 | // for the argument length of the action being called, pulling the 80 | // required number of arguments off the list, until the entire list 81 | // is empty. 82 | var action = function(args) { 83 | while (args.length > 0) { 84 | if (args.shift() == window.drawingKey) { 85 | var action = actions[args.shift()]; 86 | var argLength = action.prototype.constructor.length; 87 | action.apply(null, args.slice(0, argLength)); 88 | args = args.slice(argLength); 89 | } 90 | } 91 | }; 92 | 93 | // The main handler for actions triggered client-side. 94 | // First wrap the arguments with the drawing key and user vars, 95 | // perform the actual action client-side, and pass the action 96 | // and arguments off to the server via socket.io for broadcasting 97 | // to other users. 98 | var send = function() { 99 | var getArgs = function(args) { 100 | args = $.makeArray(args); 101 | args.unshift(window.drawingKey); 102 | args.push(window.username, window.userID); 103 | return args; 104 | }; 105 | action(getArgs(arguments)); 106 | socket.emit('message', getArgs(arguments)); 107 | }; 108 | 109 | var save = function() { 110 | var title = prompt('Save as:'); 111 | if (title) { 112 | messages.add('Sketch saved'); 113 | socket.emit('message', [window.drawingKey, 'save', title, 114 | canvas.get()[0].toDataURL('image/png')]) 115 | } 116 | dirty = false; 117 | }; 118 | 119 | // Socket.IO setup. 120 | self.socket = io.connect(':9000'); 121 | socket.on('connect', function() { 122 | send('join'); 123 | }); 124 | $(window).unload(function() { 125 | if (dirty && confirm('You have unsaved changed, would you like to save them?')) { 126 | save(); 127 | } 128 | send('leave'); 129 | }); 130 | socket.on('message', function(args) { 131 | $('#loading').remove(); 132 | if (drawing) { 133 | queue.push.apply(null, args); 134 | } else { 135 | action(args); 136 | } 137 | }); 138 | 139 | // Cross-browser pixel offset. 140 | var getCoords = function(element, event) { 141 | var offset = $(element).offset(); 142 | return {x: event.pageX - offset.left, y: event.pageY - offset.top}; 143 | }; 144 | 145 | // Stop drawing on mouseup and run any queued actions. 146 | canvas.mouseup(function() { 147 | drawing = false; 148 | if (queue.length > 0) { 149 | action(queue); 150 | queue = []; 151 | } 152 | }); 153 | 154 | // Start drawing on mousedown. 155 | canvas.mousedown(function(event) { 156 | // Stops the cursor from reverting from the 157 | // custom cursor in Chrome. 158 | event.preventDefault(); 159 | var coords = getCoords(this, event); 160 | drawing = true; 161 | send('mousedown', coords.x, coords.y, 162 | color[0], color[1], color[2], 163 | size, first); 164 | first = false; 165 | dirty = true; 166 | }); 167 | 168 | // Draw on mousemove if drawing is currently on (eg mouse is down). 169 | canvas.mousemove(function(event) { 170 | if (drawing) { 171 | var coords = getCoords(this, event); 172 | send('mousemove', coords.x, coords.y, 173 | color[0], color[1], color[2], 174 | size); 175 | } 176 | }); 177 | 178 | // Explict save. 179 | $('#save').click(function() { 180 | save() 181 | return false; 182 | }); 183 | 184 | // Color picker. 185 | $(function() { 186 | $('#color').ColorPicker({onChange: function(hsb, hex, rgb) { 187 | color = [rgb.r, rgb.g, rgb.b]; 188 | $('#color div').css({backgroundColor: '#' + hex}); 189 | }}); 190 | }); 191 | 192 | // Brush size setup 193 | $(function() { 194 | $('.brushsize').slider({ 195 | orientation: 'vertical', range: 'max', min: 1, max: 10, 196 | slide: function( event, ui ) { 197 | size = ui.value * 5; 198 | $('#amount span').html(ui.value); 199 | } 200 | }); 201 | $('#amount span').html($('.brushsize').slider('value')); 202 | }); 203 | 204 | }); 205 | -------------------------------------------------------------------------------- /static/js/global.js: -------------------------------------------------------------------------------- 1 | 2 | var messages = { 3 | 4 | add: function(msg) { 5 | $('#message-template').tmpl({msg: msg}).appendTo('#messages'); 6 | messages.show(); 7 | }, 8 | 9 | show: function() { 10 | $('.message').slideDown(); 11 | clearTimeout(messages.timeout); 12 | messages.timeout = setTimeout(function() { 13 | $('.message').fadeOut(); 14 | }, 5000); 15 | }, 16 | 17 | timeout: null 18 | 19 | }; 20 | 21 | $(function() { 22 | $('.message a').live('click', function() { 23 | $('.message').fadeOut(); 24 | return false; 25 | }); 26 | messages.show(); 27 | $('#progress').chosen().change(function() { 28 | if (this.selectedIndex > 0) { 29 | location = this[this.selectedIndex].value + '?join'; 30 | } 31 | }); 32 | $('.stars a').mouseover(function() { 33 | var stars = $(this).parent().find('a'); 34 | stars.addClass('star-off'); 35 | stars.removeClass('star-on'); 36 | for (var i = 0; i < stars.length; i++) { 37 | $(stars[i]).addClass('star-on'); 38 | $(stars[i]).removeClass('star-off'); 39 | if (stars[i] == this) { 40 | break; 41 | } 42 | } 43 | }); 44 | $('.stars a').mouseout(function() { 45 | var stars = $(this).parent().find('a'); 46 | stars.addClass('star-off'); 47 | stars.removeClass('star-on'); 48 | for (var i = 0; i < stars.length; i++) { 49 | $(stars[i]).addClass('star-on'); 50 | $(stars[i]).removeClass('star-off'); 51 | if ($(stars[i]).hasClass('actual')) { 52 | return; 53 | } 54 | } 55 | stars.addClass('star-off'); 56 | stars.removeClass('star-on'); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /static/js/jquery-ui-1.8.14.custom.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI 1.8.14 3 | * 4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI 9 | */ 10 | (function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.14", 11 | keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); 12 | b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, 13 | "overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", 14 | function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, 15 | outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); 16 | return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= 17 | 0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted= 49 | false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); 50 | ;/* 51 | * jQuery UI Slider 1.8.14 52 | * 53 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 54 | * Dual licensed under the MIT or GPL Version 2 licenses. 55 | * http://jquery.org/license 56 | * 57 | * http://docs.jquery.com/UI/Slider 58 | * 59 | * Depends: 60 | * jquery.ui.core.js 61 | * jquery.ui.mouse.js 62 | * jquery.ui.widget.js 63 | */ 64 | (function(d){d.widget("ui.slider",d.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var b=this,a=this.options,c=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f=a.values&&a.values.length||1,e=[];this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+ 65 | this.orientation+" ui-widget ui-widget-content ui-corner-all"+(a.disabled?" ui-slider-disabled ui-disabled":""));this.range=d([]);if(a.range){if(a.range===true){if(!a.values)a.values=[this._valueMin(),this._valueMin()];if(a.values.length&&a.values.length!==2)a.values=[a.values[0],a.values[0]]}this.range=d("
").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(a.range==="min"||a.range==="max"?" ui-slider-range-"+a.range:""))}for(var j=c.length;j"); 66 | this.handles=c.add(d(e.join("")).appendTo(b.element));this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(g){g.preventDefault()}).hover(function(){a.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(a.disabled)d(this).blur();else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(g){d(this).data("index.ui-slider-handle", 67 | g)});this.handles.keydown(function(g){var k=true,l=d(this).data("index.ui-slider-handle"),i,h,m;if(!b.options.disabled){switch(g.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:k=false;if(!b._keySliding){b._keySliding=true;d(this).addClass("ui-state-active");i=b._start(g,l);if(i===false)return}break}m=b.options.step;i=b.options.values&&b.options.values.length? 68 | (h=b.values(l)):(h=b.value());switch(g.keyCode){case d.ui.keyCode.HOME:h=b._valueMin();break;case d.ui.keyCode.END:h=b._valueMax();break;case d.ui.keyCode.PAGE_UP:h=b._trimAlignValue(i+(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:h=b._trimAlignValue(i-(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(i===b._valueMax())return;h=b._trimAlignValue(i+m);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(i===b._valueMin())return;h=b._trimAlignValue(i- 69 | m);break}b._slide(g,l,h);return k}}).keyup(function(g){var k=d(this).data("index.ui-slider-handle");if(b._keySliding){b._keySliding=false;b._stop(g,k);b._change(g,k);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy(); 70 | return this},_mouseCapture:function(b){var a=this.options,c,f,e,j,g;if(a.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:b.pageX,y:b.pageY});f=this._valueMax()-this._valueMin()+1;j=this;this.handles.each(function(k){var l=Math.abs(c-j.values(k));if(f>l){f=l;e=d(this);g=k}});if(a.range===true&&this.values(1)===a.min){g+=1;e=d(this.handles[g])}if(this._start(b,g)===false)return false; 71 | this._mouseSliding=true;j._handleIndex=g;e.addClass("ui-state-active").focus();a=e.offset();this._clickOffset=!d(b.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:b.pageX-a.left-e.width()/2,top:b.pageY-a.top-e.height()/2-(parseInt(e.css("borderTopWidth"),10)||0)-(parseInt(e.css("borderBottomWidth"),10)||0)+(parseInt(e.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(b,g,c);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(b){var a= 72 | this._normValueFromMouse({x:b.pageX,y:b.pageY});this._slide(b,this._handleIndex,a);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(b){var a;if(this.orientation==="horizontal"){a= 73 | this.elementSize.width;b=b.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{a=this.elementSize.height;b=b.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}a=b/a;if(a>1)a=1;if(a<0)a=0;if(this.orientation==="vertical")a=1-a;b=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+a*b)},_start:function(b,a){var c={handle:this.handles[a],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(a); 74 | c.values=this.values()}return this._trigger("start",b,c)},_slide:function(b,a,c){var f;if(this.options.values&&this.options.values.length){f=this.values(a?0:1);if(this.options.values.length===2&&this.options.range===true&&(a===0&&c>f||a===1&&c1){this.options.values[b]=this._trimAlignValue(a);this._refreshValue();this._change(null,b)}else if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;f=arguments[0];for(e=0;e=this._valueMax())return this._valueMax();var a=this.options.step>0?this.options.step:1,c=(b-this._valueMin())%a;alignValue=b-c;if(Math.abs(c)*2>=a)alignValue+=c>0?a:-a;return parseFloat(alignValue.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max}, 80 | _refreshValue:function(){var b=this.options.range,a=this.options,c=this,f=!this._animateOff?a.animate:false,e,j={},g,k,l,i;if(this.options.values&&this.options.values.length)this.handles.each(function(h){e=(c.values(h)-c._valueMin())/(c._valueMax()-c._valueMin())*100;j[c.orientation==="horizontal"?"left":"bottom"]=e+"%";d(this).stop(1,1)[f?"animate":"css"](j,a.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(h===0)c.range.stop(1,1)[f?"animate":"css"]({left:e+"%"},a.animate); 81 | if(h===1)c.range[f?"animate":"css"]({width:e-g+"%"},{queue:false,duration:a.animate})}else{if(h===0)c.range.stop(1,1)[f?"animate":"css"]({bottom:e+"%"},a.animate);if(h===1)c.range[f?"animate":"css"]({height:e-g+"%"},{queue:false,duration:a.animate})}g=e});else{k=this.value();l=this._valueMin();i=this._valueMax();e=i!==l?(k-l)/(i-l)*100:0;j[c.orientation==="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[f?"animate":"css"](j,a.animate);if(b==="min"&&this.orientation==="horizontal")this.range.stop(1, 82 | 1)[f?"animate":"css"]({width:e+"%"},a.animate);if(b==="max"&&this.orientation==="horizontal")this.range[f?"animate":"css"]({width:100-e+"%"},{queue:false,duration:a.animate});if(b==="min"&&this.orientation==="vertical")this.range.stop(1,1)[f?"animate":"css"]({height:e+"%"},a.animate);if(b==="max"&&this.orientation==="vertical")this.range[f?"animate":"css"]({height:100-e+"%"},{queue:false,duration:a.animate})}}});d.extend(d.ui.slider,{version:"1.8.14"})})(jQuery); 83 | ; -------------------------------------------------------------------------------- /static/js/jquery.tmpl.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Templates Plugin 1.0.0pre 3 | * http://github.com/jquery/jquery-tmpl 4 | * Requires jQuery 1.4.2 5 | * 6 | * Copyright Software Freedom Conservancy, Inc. 7 | * Dual licensed under the MIT or GPL Version 2 licenses. 8 | * http://jquery.org/license 9 | */ 10 | (function( jQuery, undefined ){ 11 | var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /, 12 | newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; 13 | 14 | function newTmplItem( options, parentItem, fn, data ) { 15 | // Returns a template item data structure for a new rendered instance of a template (a 'template item'). 16 | // The content field is a hierarchical array of strings and nested items (to be 17 | // removed and replaced by nodes field of dom elements, once inserted in DOM). 18 | var newItem = { 19 | data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}), 20 | _wrap: parentItem ? parentItem._wrap : null, 21 | tmpl: null, 22 | parent: parentItem || null, 23 | nodes: [], 24 | calls: tiCalls, 25 | nest: tiNest, 26 | wrap: tiWrap, 27 | html: tiHtml, 28 | update: tiUpdate 29 | }; 30 | if ( options ) { 31 | jQuery.extend( newItem, options, { nodes: [], parent: parentItem }); 32 | } 33 | if ( fn ) { 34 | // Build the hierarchical content to be used during insertion into DOM 35 | newItem.tmpl = fn; 36 | newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); 37 | newItem.key = ++itemKey; 38 | // Keep track of new template item, until it is stored as jQuery Data on DOM element 39 | (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; 40 | } 41 | return newItem; 42 | } 43 | 44 | // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). 45 | jQuery.each({ 46 | appendTo: "append", 47 | prependTo: "prepend", 48 | insertBefore: "before", 49 | insertAfter: "after", 50 | replaceAll: "replaceWith" 51 | }, function( name, original ) { 52 | jQuery.fn[ name ] = function( selector ) { 53 | var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, 54 | parent = this.length === 1 && this[0].parentNode; 55 | 56 | appendToTmplItems = newTmplItems || {}; 57 | if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { 58 | insert[ original ]( this[0] ); 59 | ret = this; 60 | } else { 61 | for ( i = 0, l = insert.length; i < l; i++ ) { 62 | cloneIndex = i; 63 | elems = (i > 0 ? this.clone(true) : this).get(); 64 | jQuery( insert[i] )[ original ]( elems ); 65 | ret = ret.concat( elems ); 66 | } 67 | cloneIndex = 0; 68 | ret = this.pushStack( ret, name, insert.selector ); 69 | } 70 | tmplItems = appendToTmplItems; 71 | appendToTmplItems = null; 72 | jQuery.tmpl.complete( tmplItems ); 73 | return ret; 74 | }; 75 | }); 76 | 77 | jQuery.fn.extend({ 78 | // Use first wrapped element as template markup. 79 | // Return wrapped set of template items, obtained by rendering template against data. 80 | tmpl: function( data, options, parentItem ) { 81 | return jQuery.tmpl( this[0], data, options, parentItem ); 82 | }, 83 | 84 | // Find which rendered template item the first wrapped DOM element belongs to 85 | tmplItem: function() { 86 | return jQuery.tmplItem( this[0] ); 87 | }, 88 | 89 | // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. 90 | template: function( name ) { 91 | return jQuery.template( name, this[0] ); 92 | }, 93 | 94 | domManip: function( args, table, callback, options ) { 95 | if ( args[0] && jQuery.isArray( args[0] )) { 96 | var dmArgs = jQuery.makeArray( arguments ), elems = args[0], elemsLength = elems.length, i = 0, tmplItem; 97 | while ( i < elemsLength && !(tmplItem = jQuery.data( elems[i++], "tmplItem" ))) {} 98 | if ( tmplItem && cloneIndex ) { 99 | dmArgs[2] = function( fragClone ) { 100 | // Handler called by oldManip when rendered template has been inserted into DOM. 101 | jQuery.tmpl.afterManip( this, fragClone, callback ); 102 | }; 103 | } 104 | oldManip.apply( this, dmArgs ); 105 | } else { 106 | oldManip.apply( this, arguments ); 107 | } 108 | cloneIndex = 0; 109 | if ( !appendToTmplItems ) { 110 | jQuery.tmpl.complete( newTmplItems ); 111 | } 112 | return this; 113 | } 114 | }); 115 | 116 | jQuery.extend({ 117 | // Return wrapped set of template items, obtained by rendering template against data. 118 | tmpl: function( tmpl, data, options, parentItem ) { 119 | var ret, topLevel = !parentItem; 120 | if ( topLevel ) { 121 | // This is a top-level tmpl call (not from a nested template using {{tmpl}}) 122 | parentItem = topTmplItem; 123 | tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); 124 | wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level 125 | } else if ( !tmpl ) { 126 | // The template item is already associated with DOM - this is a refresh. 127 | // Re-evaluate rendered template for the parentItem 128 | tmpl = parentItem.tmpl; 129 | newTmplItems[parentItem.key] = parentItem; 130 | parentItem.nodes = []; 131 | if ( parentItem.wrapped ) { 132 | updateWrapped( parentItem, parentItem.wrapped ); 133 | } 134 | // Rebuild, without creating a new template item 135 | return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); 136 | } 137 | if ( !tmpl ) { 138 | return []; // Could throw... 139 | } 140 | if ( typeof data === "function" ) { 141 | data = data.call( parentItem || {} ); 142 | } 143 | if ( options && options.wrapped ) { 144 | updateWrapped( options, options.wrapped ); 145 | } 146 | ret = jQuery.isArray( data ) ? 147 | jQuery.map( data, function( dataItem ) { 148 | return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; 149 | }) : 150 | [ newTmplItem( options, parentItem, tmpl, data ) ]; 151 | return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; 152 | }, 153 | 154 | // Return rendered template item for an element. 155 | tmplItem: function( elem ) { 156 | var tmplItem; 157 | if ( elem instanceof jQuery ) { 158 | elem = elem[0]; 159 | } 160 | while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} 161 | return tmplItem || topTmplItem; 162 | }, 163 | 164 | // Set: 165 | // Use $.template( name, tmpl ) to cache a named template, 166 | // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. 167 | // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. 168 | 169 | // Get: 170 | // Use $.template( name ) to access a cached template. 171 | // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) 172 | // will return the compiled template, without adding a name reference. 173 | // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent 174 | // to $.template( null, templateString ) 175 | template: function( name, tmpl ) { 176 | if (tmpl) { 177 | // Compile template and associate with name 178 | if ( typeof tmpl === "string" ) { 179 | // This is an HTML string being passed directly in. 180 | tmpl = buildTmplFn( tmpl ); 181 | } else if ( tmpl instanceof jQuery ) { 182 | tmpl = tmpl[0] || {}; 183 | } 184 | if ( tmpl.nodeType ) { 185 | // If this is a template block, use cached copy, or generate tmpl function and cache. 186 | tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); 187 | // Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space. 188 | // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x. 189 | // To correct this, include space in tag: foo="${ x }" -> foo="value of x" 190 | } 191 | return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; 192 | } 193 | // Return named compiled template 194 | return name ? (typeof name !== "string" ? jQuery.template( null, name ): 195 | (jQuery.template[name] || 196 | // If not in map, and not containing at least on HTML tag, treat as a selector. 197 | // (If integrated with core, use quickExpr.exec) 198 | jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; 199 | }, 200 | 201 | encode: function( text ) { 202 | // Do HTML encoding replacing < > & and ' and " by corresponding entities. 203 | return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); 204 | } 205 | }); 206 | 207 | jQuery.extend( jQuery.tmpl, { 208 | tag: { 209 | "tmpl": { 210 | _default: { $2: "null" }, 211 | open: "if($notnull_1){__=__.concat($item.nest($1,$2));}" 212 | // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) 213 | // This means that {{tmpl foo}} treats foo as a template (which IS a function). 214 | // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. 215 | }, 216 | "wrap": { 217 | _default: { $2: "null" }, 218 | open: "$item.calls(__,$1,$2);__=[];", 219 | close: "call=$item.calls();__=call._.concat($item.wrap(call,__));" 220 | }, 221 | "each": { 222 | _default: { $2: "$index, $value" }, 223 | open: "if($notnull_1){$.each($1a,function($2){with(this){", 224 | close: "}});}" 225 | }, 226 | "if": { 227 | open: "if(($notnull_1) && $1a){", 228 | close: "}" 229 | }, 230 | "else": { 231 | _default: { $1: "true" }, 232 | open: "}else if(($notnull_1) && $1a){" 233 | }, 234 | "html": { 235 | // Unecoded expression evaluation. 236 | open: "if($notnull_1){__.push($1a);}" 237 | }, 238 | "=": { 239 | // Encoded expression evaluation. Abbreviated form is ${}. 240 | _default: { $1: "$data" }, 241 | open: "if($notnull_1){__.push($.encode($1a));}" 242 | }, 243 | "!": { 244 | // Comment tag. Skipped by parser 245 | open: "" 246 | } 247 | }, 248 | 249 | // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events 250 | complete: function( items ) { 251 | newTmplItems = {}; 252 | }, 253 | 254 | // Call this from code which overrides domManip, or equivalent 255 | // Manage cloning/storing template items etc. 256 | afterManip: function afterManip( elem, fragClone, callback ) { 257 | // Provides cloned fragment ready for fixup prior to and after insertion into DOM 258 | var content = fragClone.nodeType === 11 ? 259 | jQuery.makeArray(fragClone.childNodes) : 260 | fragClone.nodeType === 1 ? [fragClone] : []; 261 | 262 | // Return fragment to original caller (e.g. append) for DOM insertion 263 | callback.call( elem, fragClone ); 264 | 265 | // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. 266 | storeTmplItems( content ); 267 | cloneIndex++; 268 | } 269 | }); 270 | 271 | //========================== Private helper functions, used by code above ========================== 272 | 273 | function build( tmplItem, nested, content ) { 274 | // Convert hierarchical content into flat string array 275 | // and finally return array of fragments ready for DOM insertion 276 | var frag, ret = content ? jQuery.map( content, function( item ) { 277 | return (typeof item === "string") ? 278 | // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. 279 | (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : 280 | // This is a child template item. Build nested template. 281 | build( item, tmplItem, item._ctnt ); 282 | }) : 283 | // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}. 284 | tmplItem; 285 | if ( nested ) { 286 | return ret; 287 | } 288 | 289 | // top-level template 290 | ret = ret.join(""); 291 | 292 | // Support templates which have initial or final text nodes, or consist only of text 293 | // Also support HTML entities within the HTML markup. 294 | ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { 295 | frag = jQuery( middle ).get(); 296 | 297 | storeTmplItems( frag ); 298 | if ( before ) { 299 | frag = unencode( before ).concat(frag); 300 | } 301 | if ( after ) { 302 | frag = frag.concat(unencode( after )); 303 | } 304 | }); 305 | return frag ? frag : unencode( ret ); 306 | } 307 | 308 | function unencode( text ) { 309 | // Use createElement, since createTextNode will not render HTML entities correctly 310 | var el = document.createElement( "div" ); 311 | el.innerHTML = text; 312 | return jQuery.makeArray(el.childNodes); 313 | } 314 | 315 | // Generate a reusable function that will serve to render a template against data 316 | function buildTmplFn( markup ) { 317 | return new Function("jQuery","$item", 318 | // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10). 319 | "var $=jQuery,call,__=[],$data=$item.data;" + 320 | 321 | // Introduce the data as local variables using with(){} 322 | "with($data){__.push('" + 323 | 324 | // Convert the template into pure JavaScript 325 | jQuery.trim(markup) 326 | .replace( /([\\'])/g, "\\$1" ) 327 | .replace( /[\r\t\n]/g, " " ) 328 | .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) 329 | .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, 330 | function( all, slash, type, fnargs, target, parens, args ) { 331 | var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; 332 | if ( !tag ) { 333 | throw "Unknown template tag: " + type; 334 | } 335 | def = tag._default || []; 336 | if ( parens && !/\w$/.test(target)) { 337 | target += parens; 338 | parens = ""; 339 | } 340 | if ( target ) { 341 | target = unescape( target ); 342 | args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); 343 | // Support for target being things like a.toLowerCase(); 344 | // In that case don't call with template item as 'this' pointer. Just evaluate... 345 | expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target; 346 | exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; 347 | } else { 348 | exprAutoFnDetect = expr = def.$1 || "null"; 349 | } 350 | fnargs = unescape( fnargs ); 351 | return "');" + 352 | tag[ slash ? "close" : "open" ] 353 | .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) 354 | .split( "$1a" ).join( exprAutoFnDetect ) 355 | .split( "$1" ).join( expr ) 356 | .split( "$2" ).join( fnargs || def.$2 || "" ) + 357 | "__.push('"; 358 | }) + 359 | "');}return __;" 360 | ); 361 | } 362 | function updateWrapped( options, wrapped ) { 363 | // Build the wrapped content. 364 | options._wrap = build( options, true, 365 | // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. 366 | jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] 367 | ).join(""); 368 | } 369 | 370 | function unescape( args ) { 371 | return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; 372 | } 373 | function outerHtml( elem ) { 374 | var div = document.createElement("div"); 375 | div.appendChild( elem.cloneNode(true) ); 376 | return div.innerHTML; 377 | } 378 | 379 | // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. 380 | function storeTmplItems( content ) { 381 | var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; 382 | for ( i = 0, l = content.length; i < l; i++ ) { 383 | if ( (elem = content[i]).nodeType !== 1 ) { 384 | continue; 385 | } 386 | elems = elem.getElementsByTagName("*"); 387 | for ( m = elems.length - 1; m >= 0; m-- ) { 388 | processItemKey( elems[m] ); 389 | } 390 | processItemKey( elem ); 391 | } 392 | function processItemKey( el ) { 393 | var pntKey, pntNode = el, pntItem, tmplItem, key; 394 | // Ensure that each rendered template inserted into the DOM has its own template item, 395 | if ( (key = el.getAttribute( tmplItmAtt ))) { 396 | while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } 397 | if ( pntKey !== key ) { 398 | // The next ancestor with a _tmplitem expando is on a different key than this one. 399 | // So this is a top-level element within this template item 400 | // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. 401 | pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; 402 | if ( !(tmplItem = newTmplItems[key]) ) { 403 | // The item is for wrapped content, and was copied from the temporary parent wrappedItem. 404 | tmplItem = wrappedItems[key]; 405 | tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] ); 406 | tmplItem.key = ++itemKey; 407 | newTmplItems[itemKey] = tmplItem; 408 | } 409 | if ( cloneIndex ) { 410 | cloneTmplItem( key ); 411 | } 412 | } 413 | el.removeAttribute( tmplItmAtt ); 414 | } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { 415 | // This was a rendered element, cloned during append or appendTo etc. 416 | // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. 417 | cloneTmplItem( tmplItem.key ); 418 | newTmplItems[tmplItem.key] = tmplItem; 419 | pntNode = jQuery.data( el.parentNode, "tmplItem" ); 420 | pntNode = pntNode ? pntNode.key : 0; 421 | } 422 | if ( tmplItem ) { 423 | pntItem = tmplItem; 424 | // Find the template item of the parent element. 425 | // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) 426 | while ( pntItem && pntItem.key != pntNode ) { 427 | // Add this element as a top-level node for this rendered template item, as well as for any 428 | // ancestor items between this item and the item of its parent element 429 | pntItem.nodes.push( el ); 430 | pntItem = pntItem.parent; 431 | } 432 | // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... 433 | delete tmplItem._ctnt; 434 | delete tmplItem._wrap; 435 | // Store template item as jQuery data on the element 436 | jQuery.data( el, "tmplItem", tmplItem ); 437 | } 438 | function cloneTmplItem( key ) { 439 | key = key + keySuffix; 440 | tmplItem = newClonedItems[key] = 441 | (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent )); 442 | } 443 | } 444 | } 445 | 446 | //---- Helper functions for template item ---- 447 | 448 | function tiCalls( content, tmpl, data, options ) { 449 | if ( !content ) { 450 | return stack.pop(); 451 | } 452 | stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); 453 | } 454 | 455 | function tiNest( tmpl, data, options ) { 456 | // nested template, using {{tmpl}} tag 457 | return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); 458 | } 459 | 460 | function tiWrap( call, wrapped ) { 461 | // nested template, using {{wrap}} tag 462 | var options = call.options || {}; 463 | options.wrapped = wrapped; 464 | // Apply the template, which may incorporate wrapped content, 465 | return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); 466 | } 467 | 468 | function tiHtml( filter, textOnly ) { 469 | var wrapped = this._wrap; 470 | return jQuery.map( 471 | jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), 472 | function(e) { 473 | return textOnly ? 474 | e.innerText || e.textContent : 475 | e.outerHTML || outerHtml(e); 476 | }); 477 | } 478 | 479 | function tiUpdate() { 480 | var coll = this.nodes; 481 | jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); 482 | jQuery( coll ).remove(); 483 | } 484 | })( jQuery ); 485 | -------------------------------------------------------------------------------- /static/js/jquery.tools.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Tools v1.2.5 - The missing UI library for the Web 3 | * 4 | * tooltip/tooltip.js 5 | * 6 | * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. 7 | * 8 | * http://flowplayer.org/tools/ 9 | * 10 | */ 11 | (function(a){a.tools=a.tools||{version:"v1.2.5"},a.tools.tooltip={conf:{effect:"toggle",fadeOutSpeed:"fast",predelay:0,delay:30,opacity:1,tip:0,position:["top","center"],offset:[0,0],relative:!1,cancelDefault:!0,events:{def:"mouseenter,mouseleave",input:"focus,blur",widget:"focus mouseenter,blur mouseleave",tooltip:"mouseenter,mouseleave"},layout:"
",tipClass:"tooltip"},addEffect:function(a,c,d){b[a]=[c,d]}};var b={toggle:[function(a){var b=this.getConf(),c=this.getTip(),d=b.opacity;d<1&&c.css({opacity:d}),c.show(),a.call()},function(a){this.getTip().hide(),a.call()}],fade:[function(a){var b=this.getConf();this.getTip().fadeTo(b.fadeInSpeed,b.opacity,a)},function(a){this.getTip().fadeOut(this.getConf().fadeOutSpeed,a)}]};function c(b,c,d){var e=d.relative?b.position().top:b.offset().top,f=d.relative?b.position().left:b.offset().left,g=d.position[0];e-=c.outerHeight()-d.offset[0],f+=b.outerWidth()+d.offset[1],/iPad/i.test(navigator.userAgent)&&(e-=a(window).scrollTop());var h=c.outerHeight()+b.outerHeight();g=="center"&&(e+=h/2),g=="bottom"&&(e+=h),g=d.position[1];var i=c.outerWidth()+b.outerWidth();g=="center"&&(f-=i/2),g=="left"&&(f-=i);return{top:e,left:f}}function d(d,e){var f=this,g=d.add(f),h,i=0,j=0,k=d.attr("title"),l=d.attr("data-tooltip"),m=b[e.effect],n,o=d.is(":input"),p=o&&d.is(":checkbox, :radio, select, :button, :submit"),q=d.attr("type"),r=e.events[q]||e.events[o?p?"widget":"input":"def"];if(!m)throw"Nonexistent effect \""+e.effect+"\"";r=r.split(/,\s*/);if(r.length!=2)throw"Tooltip: bad events configuration for "+q;d.bind(r[0],function(a){clearTimeout(i),e.predelay?j=setTimeout(function(){f.show(a)},e.predelay):f.show(a)}).bind(r[1],function(a){clearTimeout(j),e.delay?i=setTimeout(function(){f.hide(a)},e.delay):f.hide(a)}),k&&e.cancelDefault&&(d.removeAttr("title"),d.data("title",k)),a.extend(f,{show:function(b){if(!h){l?h=a(l):e.tip?h=a(e.tip).eq(0):k?h=a(e.layout).addClass(e.tipClass).appendTo(document.body).hide().append(k):(h=d.next(),h.length||(h=d.parent().next()));if(!h.length)throw"Cannot find tooltip for "+d}if(f.isShown())return f;h.stop(!0,!0);var o=c(d,h,e);e.tip&&h.html(d.data("title")),b=b||a.Event(),b.type="onBeforeShow",g.trigger(b,[o]);if(b.isDefaultPrevented())return f;o=c(d,h,e),h.css({position:"absolute",top:o.top,left:o.left}),n=!0,m[0].call(f,function(){b.type="onShow",n="full",g.trigger(b)});var p=e.events.tooltip.split(/,\s*/);h.data("__set")||(h.bind(p[0],function(){clearTimeout(i),clearTimeout(j)}),p[1]&&!d.is("input:not(:checkbox, :radio), textarea")&&h.bind(p[1],function(a){a.relatedTarget!=d[0]&&d.trigger(r[1].split(" ")[0])}),h.data("__set",!0));return f},hide:function(c){if(!h||!f.isShown())return f;c=c||a.Event(),c.type="onBeforeHide",g.trigger(c);if(!c.isDefaultPrevented()){n=!1,b[e.effect][1].call(f,function(){c.type="onHide",g.trigger(c)});return f}},isShown:function(a){return a?n=="full":n},getConf:function(){return e},getTip:function(){return h},getTrigger:function(){return d}}),a.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","),function(b,c){a.isFunction(e[c])&&a(f).bind(c,e[c]),f[c]=function(b){b&&a(f).bind(c,b);return f}})}a.fn.tooltip=function(b){var c=this.data("tooltip");if(c)return c;b=a.extend(!0,{},a.tools.tooltip.conf,b),typeof b.position=="string"&&(b.position=b.position.split(/,?\s/)),this.each(function(){c=new d(a(this),b),a(this).data("tooltip",c)});return b.api?c:this}})(jQuery); 12 | -------------------------------------------------------------------------------- /static/js/json.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-11-17 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false, regexp: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | "use strict"; 168 | 169 | function f(n) { 170 | // Format integers to have at least two digits. 171 | return n < 10 ? '0' + n : n; 172 | } 173 | 174 | if (typeof Date.prototype.toJSON !== 'function') { 175 | 176 | Date.prototype.toJSON = function (key) { 177 | 178 | return isFinite(this.valueOf()) ? 179 | this.getUTCFullYear() + '-' + 180 | f(this.getUTCMonth() + 1) + '-' + 181 | f(this.getUTCDate()) + 'T' + 182 | f(this.getUTCHours()) + ':' + 183 | f(this.getUTCMinutes()) + ':' + 184 | f(this.getUTCSeconds()) + 'Z' : null; 185 | }; 186 | 187 | String.prototype.toJSON = 188 | Number.prototype.toJSON = 189 | Boolean.prototype.toJSON = function (key) { 190 | return this.valueOf(); 191 | }; 192 | } 193 | 194 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | gap, 197 | indent, 198 | meta = { // table of character substitutions 199 | '\b': '\\b', 200 | '\t': '\\t', 201 | '\n': '\\n', 202 | '\f': '\\f', 203 | '\r': '\\r', 204 | '"' : '\\"', 205 | '\\': '\\\\' 206 | }, 207 | rep; 208 | 209 | 210 | function quote(string) { 211 | 212 | // If the string contains no control characters, no quote characters, and no 213 | // backslash characters, then we can safely slap some quotes around it. 214 | // Otherwise we must also replace the offending characters with safe escape 215 | // sequences. 216 | 217 | escapable.lastIndex = 0; 218 | return escapable.test(string) ? 219 | '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' ? c : 222 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 223 | }) + '"' : 224 | '"' + string + '"'; 225 | } 226 | 227 | 228 | function str(key, holder) { 229 | 230 | // Produce a string from holder[key]. 231 | 232 | var i, // The loop counter. 233 | k, // The member key. 234 | v, // The member value. 235 | length, 236 | mind = gap, 237 | partial, 238 | value = holder[key]; 239 | 240 | // If the value has a toJSON method, call it to obtain a replacement value. 241 | 242 | if (value && typeof value === 'object' && 243 | typeof value.toJSON === 'function') { 244 | value = value.toJSON(key); 245 | } 246 | 247 | // If we were called with a replacer function, then call the replacer to 248 | // obtain a replacement value. 249 | 250 | if (typeof rep === 'function') { 251 | value = rep.call(holder, key, value); 252 | } 253 | 254 | // What happens next depends on the value's type. 255 | 256 | switch (typeof value) { 257 | case 'string': 258 | return quote(value); 259 | 260 | case 'number': 261 | 262 | // JSON numbers must be finite. Encode non-finite numbers as null. 263 | 264 | return isFinite(value) ? String(value) : 'null'; 265 | 266 | case 'boolean': 267 | case 'null': 268 | 269 | // If the value is a boolean or null, convert it to a string. Note: 270 | // typeof null does not produce 'null'. The case is included here in 271 | // the remote chance that this gets fixed someday. 272 | 273 | return String(value); 274 | 275 | // If the type is 'object', we might be dealing with an object or an array or 276 | // null. 277 | 278 | case 'object': 279 | 280 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 281 | // so watch out for that case. 282 | 283 | if (!value) { 284 | return 'null'; 285 | } 286 | 287 | // Make an array to hold the partial results of stringifying this object value. 288 | 289 | gap += indent; 290 | partial = []; 291 | 292 | // Is the value an array? 293 | 294 | if (Object.prototype.toString.apply(value) === '[object Array]') { 295 | 296 | // The value is an array. Stringify every element. Use null as a placeholder 297 | // for non-JSON values. 298 | 299 | length = value.length; 300 | for (i = 0; i < length; i += 1) { 301 | partial[i] = str(i, value) || 'null'; 302 | } 303 | 304 | // Join all of the elements together, separated with commas, and wrap them in 305 | // brackets. 306 | 307 | v = partial.length === 0 ? '[]' : 308 | gap ? '[\n' + gap + 309 | partial.join(',\n' + gap) + '\n' + 310 | mind + ']' : 311 | '[' + partial.join(',') + ']'; 312 | gap = mind; 313 | return v; 314 | } 315 | 316 | // If the replacer is an array, use it to select the members to be stringified. 317 | 318 | if (rep && typeof rep === 'object') { 319 | length = rep.length; 320 | for (i = 0; i < length; i += 1) { 321 | k = rep[i]; 322 | if (typeof k === 'string') { 323 | v = str(k, value); 324 | if (v) { 325 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 326 | } 327 | } 328 | } 329 | } else { 330 | 331 | // Otherwise, iterate through all of the keys in the object. 332 | 333 | for (k in value) { 334 | if (Object.hasOwnProperty.call(value, k)) { 335 | v = str(k, value); 336 | if (v) { 337 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 338 | } 339 | } 340 | } 341 | } 342 | 343 | // Join all of the member texts together, separated with commas, 344 | // and wrap them in braces. 345 | 346 | v = partial.length === 0 ? '{}' : 347 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 348 | mind + '}' : '{' + partial.join(',') + '}'; 349 | gap = mind; 350 | return v; 351 | } 352 | } 353 | 354 | // If the JSON object does not yet have a stringify method, give it one. 355 | 356 | if (typeof JSON.stringify !== 'function') { 357 | JSON.stringify = function (value, replacer, space) { 358 | 359 | // The stringify method takes a value and an optional replacer, and an optional 360 | // space parameter, and returns a JSON text. The replacer can be a function 361 | // that can replace values, or an array of strings that will select the keys. 362 | // A default replacer method can be provided. Use of the space parameter can 363 | // produce text that is more easily readable. 364 | 365 | var i; 366 | gap = ''; 367 | indent = ''; 368 | 369 | // If the space parameter is a number, make an indent string containing that 370 | // many spaces. 371 | 372 | if (typeof space === 'number') { 373 | for (i = 0; i < space; i += 1) { 374 | indent += ' '; 375 | } 376 | 377 | // If the space parameter is a string, it will be used as the indent string. 378 | 379 | } else if (typeof space === 'string') { 380 | indent = space; 381 | } 382 | 383 | // If there is a replacer, it must be a function or an array. 384 | // Otherwise, throw an error. 385 | 386 | rep = replacer; 387 | if (replacer && typeof replacer !== 'function' && 388 | (typeof replacer !== 'object' || 389 | typeof replacer.length !== 'number')) { 390 | throw new Error('JSON.stringify'); 391 | } 392 | 393 | // Make a fake root object containing our value under the key of ''. 394 | // Return the result of stringifying the value. 395 | 396 | return str('', {'': value}); 397 | }; 398 | } 399 | 400 | 401 | // If the JSON object does not yet have a parse method, give it one. 402 | 403 | if (typeof JSON.parse !== 'function') { 404 | JSON.parse = function (text, reviver) { 405 | 406 | // The parse method takes a text and an optional reviver function, and returns 407 | // a JavaScript value if the text is a valid JSON text. 408 | 409 | var j; 410 | 411 | function walk(holder, key) { 412 | 413 | // The walk method is used to recursively walk the resulting structure so 414 | // that modifications can be made. 415 | 416 | var k, v, value = holder[key]; 417 | if (value && typeof value === 'object') { 418 | for (k in value) { 419 | if (Object.hasOwnProperty.call(value, k)) { 420 | v = walk(value, k); 421 | if (v !== undefined) { 422 | value[k] = v; 423 | } else { 424 | delete value[k]; 425 | } 426 | } 427 | } 428 | } 429 | return reviver.call(holder, key, value); 430 | } 431 | 432 | 433 | // Parsing happens in four stages. In the first stage, we replace certain 434 | // Unicode characters with escape sequences. JavaScript handles many characters 435 | // incorrectly, either silently deleting them, or treating them as line endings. 436 | 437 | text = String(text); 438 | cx.lastIndex = 0; 439 | if (cx.test(text)) { 440 | text = text.replace(cx, function (a) { 441 | return '\\u' + 442 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 443 | }); 444 | } 445 | 446 | // In the second stage, we run the text against regular expressions that look 447 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 448 | // because they can cause invocation, and '=' because it can cause mutation. 449 | // But just to be safe, we want to reject all unexpected forms. 450 | 451 | // We split the second stage into 4 regexp operations in order to work around 452 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 453 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 454 | // replace all simple value tokens with ']' characters. Third, we delete all 455 | // open brackets that follow a colon or comma or that begin the text. Finally, 456 | // we look to see that the remaining characters are only whitespace or ']' or 457 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 458 | 459 | if (/^[\],:{}\s]*$/ 460 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 461 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 462 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 463 | 464 | // In the third stage we use the eval function to compile the text into a 465 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 466 | // in JavaScript: it can begin a block or an object literal. We wrap the text 467 | // in parens to eliminate the ambiguity. 468 | 469 | j = eval('(' + text + ')'); 470 | 471 | // In the optional fourth stage, we recursively walk the new structure, passing 472 | // each name/value pair to a reviver function for possible transformation. 473 | 474 | return typeof reviver === 'function' ? 475 | walk({'': j}, '') : j; 476 | } 477 | 478 | // If the text is not JSON parseable, then a SyntaxError is thrown. 479 | 480 | throw new SyntaxError('JSON.parse'); 481 | }; 482 | } 483 | }()); 484 | -------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body_class %}about{% endblock %} 4 | 5 | {% block main %} 6 |
7 |

About DrawnBy

8 | {{ about|safe }} 9 |

License

10 | {{ license|safe }} 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load thumbnail core_tags %} 2 | 3 | 4 | 5 | 6 | 7 | DrawnBy - Collaborative creativity 8 | {% load compress %} 9 | {% compress css %} 10 | 11 | 12 | {% block extra_css %}{% endblock %} 13 | {% endcompress %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% block extra_js %}{% endblock %} 21 | 22 | 23 | 24 |
25 | {% for message in messages %} 26 |
{{ message }}x
27 | {% endfor %} 28 |
29 | 32 | 33 |
34 |
35 | DrawnBy 36 | 56 |
57 |
{% block main %}{% endblock %}
58 |
59 |
60 | {% if request.user.is_authenticated %} 61 | {% with request.user|photo_for_user as user_photo %} 62 | Welcome, {{ request.user.username }}| 63 | {% endwith %} 64 | Logout 65 | {% else %} 66 | Welcome, Guest| 67 | Login 68 | {% endif %} 69 | {% block footer %}{% endblock %} 70 |
71 | 77 |
78 |
79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /templates/drawing_footer.html: -------------------------------------------------------------------------------- 1 | {% load thumbnail core_tags %} 2 | 7 | 8 | People in this sketch: 9 |
    10 | {% for u in drawing.users.all %} 11 | {% with u|photo_for_user as user_photo %} 12 |
  • 13 | {% endwith %} 14 | {% endfor %} 15 |
16 | 19 | {% if drawing %} 20 | Rating:{% include "rating.html" %} 21 | {% endif %} 22 | 32 | -------------------------------------------------------------------------------- /templates/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extra_js %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% endblock %} 11 | 12 | {% block extra_css %} 13 | 14 | 15 | {% endblock %} 16 | 17 | {% block body_class %}{% if request.META.QUERY_STRING != "join" %}new{% endif %}{% endblock %} 18 | 19 | {% block main %} 20 | 25 | 26 |
27 | Brush 28 |
29 |
30 |
31 | Save 32 |
33 | 34 |

Loading...

35 | 36 | 37 | {% endblock %} 38 | 39 | {% block footer %}{% include "drawing_footer.html" %}{% endblock %} 40 | -------------------------------------------------------------------------------- /templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 |

Oops! Something went wrong Please try again.

5 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body_class %}home{% endblock %} 4 | 5 | {% block main %} 6 |

DrawnBy lets you draw and sketch artwork collaboratively with your friends!
Sign in below using your Facebook or Twitter account and to get started.

7 | 11 | Use DrawnBy to draw and sketch with friends. Check out the gallery, vote & add your sketches 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load thumbnail core_tags pagination_tags %} 3 | 4 | {% block body_class %}gallery{% endblock %} 5 | 6 | {% block main %} 7 | {% autopaginate drawings 10 %} 8 | 18 | {% endblock %} 19 | 20 | {% block footer %} 21 | {% autopaginate drawings 10 %} 22 | {% paginate %} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block main %} 4 |

Oops! You'll need to login first. Use one of the services below to login.

5 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /templates/rating.html: -------------------------------------------------------------------------------- 1 | {% spaceless %} 2 | 3 | {% for score in "12345" %} 4 | {% with score|get_digit:1 as score %} 5 | 6 | {% endwith %} 7 | {% endfor %} 8 | 9 | {% endspaceless %} 10 | -------------------------------------------------------------------------------- /templates/view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body_class %}gallery{% endblock %} 4 | 5 | {% block main %} 6 |

{{ drawing }}

7 | 8 | {% endblock %} 9 | 10 | {% block footer %}{% include "drawing_footer.html" %}{% endblock %} 11 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf.urls.defaults import * 3 | from django.conf import settings 4 | from django.contrib import admin 5 | 6 | 7 | admin.autodiscover() 8 | 9 | urlpatterns = patterns("", 10 | ("^admin/", include(admin.site.urls)), 11 | url("", include("core.urls")), 12 | url("", include("social_auth.urls")), 13 | ("^%s/(?P.*)$" % settings.MEDIA_URL.strip("/"), 14 | "django.views.static.serve", {"document_root": settings.MEDIA_ROOT}), 15 | ) 16 | --------------------------------------------------------------------------------