`_.
25 |
26 | Master needs: flask, requests, sqlalchemy, wtforms, hotqueue
27 | Slave needs: hotqueue, requests
28 | p4a-build needs: requests
29 |
30 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/templates/frontend/_formhelpers.html:
--------------------------------------------------------------------------------
1 | {% macro form_field(field) %}
2 | {{ field.label }}
3 | {{ field(placeholder=field.description, **kwargs)|safe }}
4 | {% if field.errors %}
5 |
6 | {% for error in field.errors %}
7 | {{ error }}
8 | {% endfor %}
9 |
10 | {% endif %}
11 | {% endmacro %}
12 |
13 | {% macro form_begin(form) %}
14 | {% if form.csrf.errors %}
15 |
16 | {% for error in form.csrf.errors %}
17 | {{ error }}
18 | {% endfor %}
19 |
20 | {% endif %}
21 | {{ form.hidden_tag() }}
22 | {% endmacro %}
23 |
24 | {% macro form_field_hidden(name, value) %}
25 |
26 | {% endmacro %}
27 |
28 | {% macro form_end(form) %}
29 | {{ form.submit }}
30 | {% endmacro %}
31 |
32 | {% macro form_info(title, value) %}
33 | {{ title }}
34 |
35 | {% endmacro %}
36 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/static/js/jquery.defaultvalue.js:
--------------------------------------------------------------------------------
1 | (function(c){c.fn.extend({defaultValue:function(e){if("placeholder"in document.createElement("input"))return!1;return this.each(function(){if(c(this).data("defaultValued"))return!1;var a=c(this),h=a.attr("placeholder"),f={input:a};a.data("defaultValued",!0);var d=function(){var b;if(a.context.nodeName.toLowerCase()=="input")b=c(" ").attr({type:"text"});else if(a.context.nodeName.toLowerCase()=="textarea")b=c("");else throw"DefaultValue only works with input and textareas";b.attr({value:h,
2 | "class":a.attr("class")+" empty",size:a.attr("size"),style:a.attr("style"),tabindex:a.attr("tabindex"),rows:a.attr("rows"),cols:a.attr("cols"),name:"defaultvalue-clone-"+((1+Math.random())*65536|0).toString(16).substring(1)});b.focus(function(){b.hide();a.show();setTimeout(function(){a.focus()},1)});return b}();f.clone=d;d.insertAfter(a);var g=function(){a.val().length<=0?(d.show(),a.hide()):(d.hide(),a.show().trigger("click"))};a.bind("blur",g);g();e&&e(f)})}})})(jQuery);
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2017 Kivy Team and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/master/web/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = ('app', )
2 |
3 | from flask import Flask
4 | from web.config import config
5 | from time import time, localtime, strftime
6 |
7 | def delta_time(seconds):
8 | seconds = int(seconds)
9 | minutes = seconds // 60
10 | hours = minutes // 60
11 | minutes = minutes % 60
12 | days = hours // 24
13 | hours = hours % 24
14 | seconds = seconds % 60
15 | k = {'seconds': seconds, 'minutes': minutes,
16 | 'hours': hours, 'days': days}
17 | if days:
18 | return '%(days)sj %(hours)dh %(minutes)dm %(seconds)ds' % k
19 | elif hours:
20 | return '%(hours)dh %(minutes)dm %(seconds)ds' % k
21 | elif minutes:
22 | return '%(minutes)dm %(seconds)ds' % k
23 | else:
24 | return '%(seconds)ds' % k
25 |
26 |
27 | app = Flask(__name__)
28 | app.secret_key = config.get('www', 'secret_key')
29 | app.jinja_env.globals['time'] = time
30 | app.jinja_env.filters['delta_time'] = delta_time
31 |
32 | @app.template_filter('datetimeformat')
33 | def datetimeformat(value, format='%d/%m/%Y %H:%M'):
34 | try:
35 | r=strftime(format, localtime(float(value)))
36 | #"%Y-%m-%d %H:%M:%S"
37 | except:
38 | r=''
39 | return r
40 |
41 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/templates/frontend/help.html:
--------------------------------------------------------------------------------
1 | {% extends "frontend/layout.html" %}
2 | {% block content %}
3 |
4 |
5 |
6 | What is it?
7 |
8 | This is a service for building your APK from your python source code. We
9 | know that it can be a pain to compile the python source code, modules, and put
10 | everything in an APK to make it work. With some constraint, we are able to do
11 | that for you.
12 |
The whole website is based on top of Python for Android project.
13 |
14 | How it works?
15 |
16 | You submit a zip file containing your python source code, and give some information like the package name, title, version, python modules to include etc. Then when available, a cloud builder will build your project, and give you an APK in debug or release mode, on an URL or email.
17 |
18 | Where to start ?
19 |
20 | You can read how to create the Hello
22 | world example . Then you can use our p4a-build tool for pushing your application on this service
23 |
24 |
25 |
26 |
27 |
28 | {% endblock %}
29 |
--------------------------------------------------------------------------------
/master/web/config.py:
--------------------------------------------------------------------------------
1 | __all__ = ('config', 'logger', )
2 |
3 | from hotqueue import HotQueue
4 | from ConfigParser import ConfigParser
5 | from os.path import dirname, join, exists
6 | from redis import Redis
7 | import json
8 |
9 | # configuration
10 | config = ConfigParser()
11 | config.add_section('www'),
12 | config.set('www', 'baseurl', 'http://android.kivy.org/')
13 | config.set('www', 'secret_key', '')
14 |
15 | config.add_section('database')
16 | config.set('database', 'url', 'sqlite:////tmp/test.db')
17 |
18 | config.add_section('redis')
19 | config.set('redis', 'host', 'localhost')
20 | config.set('redis', 'port', '6379')
21 | config.set('redis', 'password', '')
22 |
23 | # read existing file
24 | config_fn = join(dirname(__file__), '..', 'config.cfg')
25 | if exists(config_fn):
26 | config.read(config_fn)
27 |
28 | # write current config if possible
29 | try:
30 | fd = open(config_fn, 'w')
31 | config.write(fd)
32 | fd.close()
33 | except Exception:
34 | pass
35 |
36 | # start the queue
37 | qjob = HotQueue(
38 | 'jobsubmit',
39 | host=config.get('redis', 'host'),
40 | port=config.getint('redis', 'port'),
41 | password=config.get('redis', 'password'),
42 | db=0)
43 |
44 | # Redis database connector
45 | r = Redis(
46 | host=config.get('redis', 'host'),
47 | port=config.getint('redis', 'port'),
48 | password=config.get('redis', 'password'),
49 | db=1)
50 |
51 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/static/js/genero.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('form input[type="text"]').not('[readonly=readonly]').first().select().focus();
3 | $('div.flash-message').hide().slideDown().delay(5000).slideUp();
4 | $('div.flash-error').hide().slideDown();
5 | //$('input[type="submit"]').button();
6 | $('a.btn-back').button({ icons: { primary: 'ui-icon-arrowreturn-1-w' }});
7 | $('a.btn-add').button({ icons: { primary: 'ui-icon-plus' }});
8 | $('a.btn-del').button({ icons: { primary: 'ui-icon-circle-close' }});
9 | $('a.btn-edit').button({ icons: { primary: 'ui-icon-pencil' }});
10 | $('a.btn-mod').button({ icons: { primary: 'ui-icon-cancel' }});
11 | $('a.btn-moderate').button({ icons: { primary: 'ui-icon-pencil' }});
12 | $('a.btn-check').button({ icons: { primary: 'ui-icon-circle-check' }});
13 | $('a.btn-search').button({ icons: { primary: 'ui-icon-search' }});
14 | $('a.btn-big').button()
15 |
16 | $('input[name="dt_start"]').datetimepicker({
17 | 'dateFormat': 'yy-mm-dd',
18 | 'timeFormat': 'hh:mm:ss',
19 | 'separator': ' '
20 | });
21 |
22 | $('input[name="dt_end"]').datetimepicker({
23 | 'dateFormat': 'yy-mm-dd',
24 | 'timeFormat': 'hh:mm:ss',
25 | 'separator': ' '
26 | });
27 |
28 | $('input[name="dt_event"]').datetimepicker({
29 | 'dateFormat': 'yy-mm-dd',
30 | 'timeFormat': 'hh:mm:ss',
31 | 'separator': ' '
32 | });
33 |
34 | $(' [placeholder] ').defaultValue();
35 |
36 | $('#expertmode').click(function() {
37 | $('#expertdiv').show();
38 | $('#expertmode').hide();
39 | })
40 | });
41 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/templates/frontend/index.html:
--------------------------------------------------------------------------------
1 | {% from "frontend/_formhelpers.html" import form_begin, form_field, form_end %}
2 |
3 | {% extends "frontend/layout.html" %}
4 | {% block body_class %}focus{% endblock%}
5 | {% block content %}
6 |
7 | Create Android APK package from your Python source code using Python for android . Tell me more »
8 |
9 |
10 |
11 |
Web submission
12 |
13 |
32 |
33 |
34 |
35 |
Command line
36 |
37 |
Install p4a-build tool:
38 |
pip install p4a-build
39 |
Usage:
40 |
p4a-build \
41 | --package org.kivy.touchtracer \
42 | --name Touchtracer \
43 | --version 1.0 \
44 | --modules "pil kivy" \
45 | --dir /path/to/app
46 |
47 |
48 |
49 | {% endblock %}
50 |
--------------------------------------------------------------------------------
/slave/proxycache.py:
--------------------------------------------------------------------------------
1 | import BaseHTTPServer
2 | import hashlib
3 | import os
4 | import requests
5 | import urllib2
6 |
7 | cache_ext = ('.zip', '.tgz', '.tbz2', '.tar.gz', '.tar.bz2')
8 |
9 | class CacheHandler(BaseHTTPServer.BaseHTTPRequestHandler):
10 | def allowed(self, p):
11 | for ext in cache_ext:
12 | if p.endswith(ext):
13 | return True
14 | def do_GET(self):
15 | if self.allowed(self.path):
16 | m = hashlib.md5()
17 | m.update(self.path)
18 | cache_filename = os.path.join('cache', m.hexdigest())
19 | if os.path.exists(cache_filename):
20 | print "Cache hit", self.path
21 | with open(cache_filename) as fd:
22 | data = fd.read()
23 | code = 200
24 | else:
25 | print "Cache miss", self.path
26 | #r = requests.get(self.path)
27 | #data = r.raw.read()
28 | #code = r.status_code
29 | r = urllib2.urlopen(self.path)
30 | data = r.read()
31 | code = r.getcode()
32 | with open(cache_filename, 'wb') as fd:
33 | fd.write(data)
34 | else:
35 | #r = requests.get(self.path)
36 | #data = r.raw.read()
37 | #code = r.status_code
38 | r = urllib2.urlopen(self.path)
39 | data = r.read()
40 | code = r.getcode()
41 |
42 | self.send_response(code)
43 | self.end_headers()
44 | self.wfile.write(data)
45 |
46 | def run():
47 | server_address = ('', 8000)
48 | httpd = BaseHTTPServer.HTTPServer(server_address, CacheHandler)
49 | httpd.serve_forever()
50 |
51 | if __name__ == '__main__':
52 | run()
53 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/templates/frontend/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Python for Android - Builder
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {% block pagetitle %}{% endblock %}
25 |
26 |
27 | {% for category, msg in get_flashed_messages(with_categories=true) %}
28 |
{{ msg }}
29 | {% endfor %}
30 | {% block content %}{% endblock %}
31 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/templates/frontend/job.html:
--------------------------------------------------------------------------------
1 | {% extends "frontend/layout.html" %}
2 | {% block title %}P4A Build Cloud - Job {{ job.uid }} {% endblock %}
3 | {% block content %}
4 |
5 | {% if job.is_failed != '1' and job.is_done != '1' %}
6 |
7 | {% endif %}
8 |
9 |
10 |
11 |
12 |
Status
13 |
14 |
{{ job.package_title }}, version {{ job.package_version }}
15 | {% if job.is_failed == '1' %}
16 | have failed building.
17 | {% elif job.is_done == '1' %}
18 | is done.
19 | Finished at {{ job.dt_done|datetimeformat }}
20 | {% elif job.is_started == '1' %}
21 | is building.
22 | Started at {{ job.dt_started|datetimeformat }}
23 | {% else %}
24 | is waiting in queue.
25 | Waiting from {{ job.dt_added|datetimeformat }}
26 | {% endif %}
27 |
28 |
29 |
{{status}}
30 |
31 |
32 |
33 |
34 |
Actions
35 |
36 |
42 |
43 |
44 |
45 |
46 | Logs
47 |
48 |
49 | {% for line in joblog %}{{ line.replace(
50 | '>', '<').replace(
51 | '<', '>').replace(
52 | '\n', ' ').replace(
53 | '[34;01m', '').replace(
54 | '[31;01m', '').replace(
55 | '[30;01m', '').replace(
56 | '[39;49;00m', ' ')|safe }}
57 | {% endfor %}
58 |
59 |
60 | Options
61 |
62 |
63 | Name: {{ job.package_name }}
64 | Title: {{ job.package_title }}
65 | Version: {{ job.package_version }}
66 | Modules: {{ job.modules }}
67 | Orientation: {{ job.package_orientation }}
68 | {% if job.package_permissions != '' %}
69 | Permissions: {{ job.package_permissions }}
70 | {% endif %}
71 | {% if job.emails != '' %}
72 | Notification emails: {{ job.emails }}
73 | {% endif %}
74 |
75 |
76 | {% endblock %}
77 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/templates/frontend/about.html:
--------------------------------------------------------------------------------
1 | {% extends "frontend/layout.html" %}
2 | {% block content %}
3 |
4 |
5 |
6 | What is it?
7 |
8 | This is a service for building your APK from your python source code. We
9 | know that it can be a pain to compile the python source code, modules, and put
10 | everything in an APK to make it work. With some constraint, we are able to do
11 | that for you.
12 |
The whole website is based on top of Python for Android project.
13 |
14 | How it works?
15 |
16 | You submit a zip file containing your python source code, and give some information like the package name, title, version, python modules to include etc. Then when available, a cloud builder will build your project, and give you an APK in debug or release mode, on an URL or email.
17 |
18 | All the jobs are trashed (failed or done) after 48h, or as soon as you click on Delete button. We are not keeping any informations from your build.
19 |
20 | Where to start ?
21 |
22 | You can read how to create the Hello
24 | world example . Then you can use our p4a-build tool for pushing your application on this service
25 |
26 | pip install --upgrade p4a-build
27 | p4a-build --package com.test.helloworld --name 'Hello world' --version '1.0' \
28 | --modules 'kivy' --dir /path/to/helloworld
29 |
30 |
31 | It will give you a Hello-world-1.0-debug.apk
32 |
33 | How to install an APK?
34 |
35 | If you have created a debug APK, then you need to use adb tool from the android SDK, and write:
36 | adb install -r Hello-world-1.0-debug.apk
37 |
38 | How to sign a release APK?
39 |
40 | If you've created a release APK (with --release), you will need to sign the package for beeing able to install it in any android device, and to submit it on the android market.
41 | Go to Signing Your Applications from the android guide, in the section "Signing Release Mode"
42 | Basically, it can be done in 3 steps when you have your keystore available:
43 |
44 | jarsigner -verbose -keystore /path/to/your/key.keystore \
45 | Hello-world-1.0-release-unsigned.apk my_keyname
46 | jarsigner -verbose -verify Hello-world-1.0-release-unsigned.apk
47 | zipalign -v 4 Hello-world-1.0-release-unsigned.apk Hello-world-1.0.apk
48 |
49 |
50 |
51 |
52 | How can i participate?
53 |
54 | Take a look at the Python for android - cloud project on github
55 |
56 | {% endblock %}
57 |
--------------------------------------------------------------------------------
/master/web/job.py:
--------------------------------------------------------------------------------
1 | __all__ = ('read_job', 'read_logs')
2 |
3 |
4 | import subprocess
5 | from os.path import dirname, join, realpath
6 | from web.config import config, r
7 |
8 | class QueryDict(dict):
9 | # taken from kivy
10 | def __getattr__(self, attr):
11 | try:
12 | return self.__getitem__(attr)
13 | except KeyError:
14 | try:
15 | return super(QueryDict, self).__getattr__(attr)
16 | except AttributeError:
17 | raise KeyError(attr)
18 |
19 | def __setattr__(self, attr, value):
20 | self.__setitem__(attr, value)
21 |
22 |
23 | class JobObj(QueryDict):
24 | @property
25 | def directory(self):
26 | return realpath(join(dirname(__file__), '..', 'jobs', self.uid))
27 |
28 | @property
29 | def data_fn(self):
30 | return join(self.directory, 'data%s' % self.data_ext)
31 |
32 | @property
33 | def icon_fn(self):
34 | return join(self.directory, 'icon.png')
35 |
36 | @property
37 | def presplash_fn(self):
38 | return join(self.directory, 'presplash.png')
39 |
40 | @property
41 | def apk_fn(self):
42 | return join(self.directory, self.apk)
43 |
44 | def notify(self):
45 | if len(self.emails) == 0:
46 | return
47 | status = 'failed build' if self.is_failed == '1' else 'finished'
48 | subject = '[p4a] Build %s, version %s %s' % (
49 | self.package_title,
50 | self.package_version,
51 | status)
52 | if self.is_failed == '1':
53 | content = ('Hi,\n\nYour package %s failed to build.\n\n'
54 | 'Informations: %s\n\nP4A Build Cloud.') % (
55 | self.package_title,
56 | '%s/job/%s' % (config.get('www', 'baseurl'),self.uid))
57 | else:
58 | content = ('Hi,\n\nYour package %s is available.\n\n'
59 | 'APK: %s\nInformations: %s\n\nEnjoy,\n\nP4A Build Cloud.') % (
60 | self.package_title,
61 | '%s/download/%s/%s' % (config.get('www', 'baseurl'),
62 | self.uid, self.apk),
63 | '%s/job/%s' % (config.get('www', 'baseurl'),self.uid))
64 |
65 | for email in self.emails.split():
66 | cmd = ['mail', '-s', subject, '-a', 'From: p4a-noreply@kivy.org',
67 | email]
68 | p = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE)
69 | p.stdin.write(content)
70 | p.stdin.close()
71 | p.communicate()
72 |
73 | def read_entry(basekey, cls=QueryDict, *keyswanted):
74 | keys = r.keys('%s*' % basekey)
75 | entry = cls()
76 | for key in keyswanted:
77 | entry[key] = None
78 | for key in keys:
79 | skey = key[len(basekey):]
80 | if keyswanted and skey not in keyswanted:
81 | continue
82 | entry[skey] = r.get(key)
83 | return entry
84 |
85 | def read_logs(uid):
86 | return read_entry('log:%s:' % uid).values()
87 |
88 | def read_job(uid, *keys):
89 | if not r.keys('job:%s' % uid):
90 | return None
91 | job = read_entry('job:%s:' % uid, JobObj, *keys)
92 | job['uid'] = uid
93 | return job
94 |
95 |
--------------------------------------------------------------------------------
/master/pollqlog.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | '''
3 | Poll the log queue (builder -> webservice).
4 | Must be started on the same server than the web service.
5 | '''
6 |
7 | import sys
8 | import os
9 | from os.path import dirname, join
10 | sys.path.insert(0, dirname(__file__))
11 | activate_this = join(dirname(__file__), '.env', 'bin', 'activate_this.py')
12 | execfile(activate_this, dict(__file__=activate_this))
13 |
14 | from web.config import config, r
15 | from hotqueue import HotQueue
16 | from web.job import read_job, JobObj
17 | from time import time, sleep
18 |
19 | # start a thread for listing qlog
20 | qlog = HotQueue(
21 | 'joblog',
22 | host=config.get('redis', 'host'),
23 | port=config.getint('redis', 'port'),
24 | password=config.get('redis', 'password'),
25 | db=0)
26 |
27 | qlog.put({'cmd': 'purge'})
28 |
29 | def loop_handle(log):
30 | if 'cmd' not in log:
31 | return
32 | cmd = log['cmd']
33 |
34 | if 'host' in log:
35 | r.set('host:{}:last_alive'.format(log['host']), time())
36 |
37 | if cmd in ('available', 'busy'):
38 | r.set('host:{}:status'.format(log['host']), cmd)
39 | return
40 |
41 | if cmd == 'purge':
42 | # search all projects done last 48h
43 | keys = r.keys('job:*:dt_added')
44 | print keys
45 | to_delete = []
46 | ctime = time()
47 | for key in keys:
48 | dt = ctime - float(r.get(key))
49 | print 'checked', key[4:-9], dt
50 | if dt > 60 * 60 * 48:
51 | print 'going to delete', key[4:-9], ', delta is', dt
52 | to_delete.append(key[4:-9])
53 |
54 | # delete everything related to all the jobs to delete
55 | print 'doing deletion of', to_delete
56 | for uid in to_delete:
57 | print 'delete', uid
58 | job = JobObj({'uid': uid})
59 | d = job.directory
60 | if d and len(d) > 10:
61 | subprocess.Popen(['rm', '-rf', d], shell=False).communicate()
62 | keys = r.keys('job:%s*' % uid)
63 | if keys:
64 | r.delete(*keys)
65 | keys = r.keys('log:%s*' % uid)
66 | if keys:
67 | r.delete(*keys)
68 |
69 | return
70 |
71 | # next command _need_ uid.
72 |
73 | if 'uid' not in log:
74 | return
75 | uid = log['uid']
76 |
77 | jobkey = 'job:%s' % uid
78 | if not r.keys(jobkey):
79 | return
80 |
81 | if cmd == 'status':
82 | r.set(jobkey + ':build_status', log['msg'])
83 | elif cmd == 'exception':
84 | r.set(jobkey + ':build_status', '[1/1] Error')
85 | r.set(jobkey + ':is_failed', 1)
86 | r.set(jobkey + ':fail_message', log['msg'])
87 |
88 | if cmd == 'log' or cmd == 'exception':
89 | logkey = 'log:%s' % uid
90 | if not r.keys(logkey):
91 | r.set(logkey, 1)
92 | lidx = 1
93 | else:
94 | lidx = r.incr(logkey)
95 | logkeytxt = logkey + ':%d' % lidx
96 | if cmd == 'log':
97 | r.set(logkeytxt, log['line'])
98 | else:
99 | r.set(logkeytxt, log['msg'])
100 |
101 | if cmd == 'exception':
102 | job = read_job(uid)
103 | if job:
104 | job.notify()
105 |
106 |
107 | if __name__ == '__main__':
108 | while True:
109 | # trigger a purge every hour
110 | qlog.put({'cmd': 'purge'})
111 | for log in qlog.consume(timeout=3600):
112 | loop_handle(log)
113 | # sleep only 1 second if we keep asking break
114 | sleep(1)
115 |
116 |
--------------------------------------------------------------------------------
/tools/p4a-build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | host = 'http://android.kivy.org/'
4 |
5 | import json
6 | import os
7 | import re
8 | import requests
9 | import tempfile
10 | import webbrowser
11 | import zipfile
12 |
13 | def zipper(d, zip_file):
14 | zfd = zipfile.ZipFile(zip_file, 'w', compression=zipfile.ZIP_DEFLATED)
15 | root_len = len(os.path.abspath(d))
16 | for root, dirs, files in os.walk(d):
17 | archive_root = os.path.abspath(root)[root_len:]
18 | for f in files:
19 | fullpath = os.path.join(root, f)
20 | archive_name = os.path.join(archive_root, f)
21 | zfd.write(fullpath, archive_name, zipfile.ZIP_DEFLATED)
22 | zfd.close()
23 | return zip_file
24 |
25 |
26 | def submit_package(args):
27 | url_form = args.host
28 | url_submit = args.host + 'submit'
29 |
30 | s = requests.session()
31 |
32 | # first time, just get the csrf token
33 | r = s.get(url_form)
34 | csrf = re.search(u'name="csrf" type="hidden" value="(.*)"',
35 | r.content).groups()[0]
36 |
37 | # create a zip of the whole directory
38 | zfd, zfn = tempfile.mkstemp(suffix='.zip')
39 | zipper(args.dir, zfn)
40 |
41 | # build the data and push it
42 | payload = {
43 | 'package_name': args.package,
44 | 'package_version': args.version,
45 | 'package_title': args.name,
46 | 'package_permissions':
47 | ' '.join(args.permissions) if args.permissions else '',
48 | 'package_orientation': args.orientation,
49 | 'emails': ' '.join(args.notify) if args.notify else '',
50 | 'modules': args.modules,
51 | 'csrf': csrf,
52 | 'batch': '1'}
53 | if args.release:
54 | payload['release'] = 'release'
55 |
56 | fd = open(zfn, 'rb')
57 | files = {'directory': (zfn, fd)}
58 |
59 | if args.icon:
60 | fdicon = open(args.icon, 'rb')
61 | files['package_icon'] = (os.path.basename(args.icon), fdicon)
62 |
63 | if args.presplash:
64 | fdpresplash = open(args.presplash, 'rb')
65 | files['package_presplash'] = (os.path.basename(args.presplash),
66 | fdpresplash)
67 |
68 | r = s.post(url_submit, data=payload, files=files)
69 | r.raise_for_status()
70 |
71 | fd.close()
72 | if args.icon:
73 | fdicon.close()
74 | if args.presplash:
75 | fdpresplash.close()
76 |
77 | data = json.loads(r.text)
78 |
79 | print
80 | print 'Package submitted to P4A Cloud.'
81 | print 'Get the build progress at:', data['url']
82 | print
83 |
84 | webbrowser.open_new_tab(data['url'])
85 |
86 | if __name__ == '__main__':
87 | import argparse
88 |
89 | ap = argparse.ArgumentParser(description='''\
90 | P4A Build tool using P4A Cloud.
91 | ''')
92 |
93 | ap.add_argument('--package', dest='package', required=True,
94 | help='The name of the java package the project will be packaged under.')
95 | ap.add_argument('--name', dest='name', required=True,
96 | help='The human-readable name of the project.')
97 | ap.add_argument('--version', dest='version', required=True,
98 | help=('The version number of the project.'
99 | 'This should consist of numbers and dots, and should have the same '
100 | 'number of groups of numbers as previous versions.'))
101 | ap.add_argument('--dir', dest='dir', required=True,
102 | help='The directory containing files for the project.')
103 | ap.add_argument('--modules', dest='modules', required=True,
104 | help='Modules to compile with the program')
105 | ap.add_argument('--icon', dest='icon',
106 | help='Path to an icon (.png)')
107 | ap.add_argument('--presplash', dest='presplash',
108 | help='Path to an presplash image (.png)')
109 | ap.add_argument('--permission', dest='permissions', action='append',
110 | help='The android permissions needed for this app.')
111 | ap.add_argument('--orientation', dest='orientation', default='landscape',
112 | help='Orientation of the application. One of "landscape" or "portrait"')
113 | ap.add_argument('--release', dest='release', action='store_true',
114 | help='If set, then a release package will be done')
115 | ap.add_argument('--notify', dest='notify', action='append',
116 | help='Email to send a notification when build is finished')
117 |
118 | ap.add_argument('--host', dest='host', default=host,
119 | help='Host to push the P4A build request')
120 | args = ap.parse_args()
121 |
122 | submit_package(args)
123 |
--------------------------------------------------------------------------------
/master/web/table.py:
--------------------------------------------------------------------------------
1 | '''
2 | DataTable: a flask/sqlachemy module to generate HTML with server side data.
3 |
4 | The project use datatable from http://www.datatables.net/
5 | '''
6 | __all__ = ('Table', 'Column')
7 |
8 | import simplejson
9 | from flask import url_for, request
10 | from sqlalchemy import asc, desc
11 |
12 | class Column(object):
13 | def __init__(self, name, field, display=None, formatter=None, width=None):
14 | super(Column, self).__init__()
15 | self.name = name
16 | self.field = field
17 | self.display = display
18 | self.formatter = formatter
19 | self.width = width
20 |
21 | def __html__(self):
22 | return '%s ' % self.name
23 |
24 | def __js_def__(self, index, out):
25 | if self.width:
26 | out.append({'sWidth': self.width, 'aTargets': [index]})
27 |
28 | def get_field(self, entry):
29 | if self.display:
30 | value = self.display(entry)
31 | else:
32 | value = getattr(entry, self.field)
33 | if self.formatter:
34 | return self.formatter(value)
35 | return value
36 |
37 | class Table(object):
38 | __uniqid = 0
39 | db_table = None
40 | db_session = None
41 | display_length = 20
42 | activate_sort = True
43 | activate_info = True
44 | activate_paginate = True
45 | activate_scroll_infinite = False
46 | activate_filter = True
47 | activate_length_change = True
48 | activate_scroll_collapse = True
49 | pagination_type = 'full_numbers'
50 | scroll_x = ''
51 | scroll_y = ''
52 | href_link = None
53 |
54 | def __init__(self, **kwargs):
55 | super(Table, self).__init__()
56 | self.html_id = kwargs.get('html_id', None)
57 | if self.html_id is None:
58 | Table.__uniqid += 1
59 | self.html_id = 'datatable%d' % Table.__uniqid
60 |
61 | def query(self):
62 | return self.db_session.query(self.db_table)
63 |
64 | def ajax(self):
65 | q = self.query()
66 |
67 | # total number of entries
68 | count = q.count()
69 |
70 | # search
71 | if 'sSearch' in request.args:
72 | search = None
73 | for col in self.columns:
74 | field = getattr(self.db_table, col.field)
75 | field = field.like('%%%s%%' % request.args['sSearch'])
76 | if search is None:
77 | search = field
78 | else:
79 | search = search | field
80 | q = q.filter(search)
81 |
82 | # sorting
83 | if 'iSortingCols' in request.args:
84 | field = self.columns[int(request.args['iSortCol_0'])].field
85 | db_field = getattr(self.db_table, field)
86 | if request.args['sSortDir_0'] == 'asc':
87 | db_field = asc(db_field)
88 | else:
89 | db_field = desc(db_field)
90 | q = q.order_by(db_field)
91 |
92 | # get the number after filter
93 | count_filtered = q.count()
94 |
95 | # pagination
96 | if self.activate_scroll_infinite:
97 | limit = self.display_length
98 | else:
99 | limit = request.args['iDisplayLength']
100 | offset = request.args['iDisplayStart']
101 | entries = q.offset(offset).limit(limit)
102 |
103 | # construct the output
104 | data = []
105 | columns = self.columns
106 | for entry in entries:
107 | data.append([col.get_field(entry) for col in columns])
108 |
109 | return simplejson.dumps({
110 | 'sEcho': request.args['sEcho'],
111 | 'iTotalRecords': count,
112 | 'iTotalDisplayRecords': count_filtered,
113 | 'aaData': data
114 | })
115 |
116 | def __json_columns_defs__(self):
117 | out = []
118 | for index, col in enumerate(self.columns):
119 | col.__js_def__(index, out)
120 | return simplejson.dumps(out)
121 |
122 | def __js_rowclick__(self):
123 | return ''
124 |
125 | def __html_columns__(self):
126 | out = ['']
127 | for col in self.columns:
128 | out.append(col.__html__())
129 | out.append(' ')
130 | return ''.join(out)
131 |
132 | def __html__(self):
133 | data = {
134 | 'html_id': self.html_id,
135 | 'columns': self.__html_columns__(),
136 | 'click_callback': self.__js_rowclick__(),
137 |
138 | # datatable
139 | 'iDisplayLength': str(int(self.display_length)),
140 | 'bSort': str(bool(self.activate_sort)).lower(),
141 | 'bInfo': str(bool(self.activate_info)).lower(),
142 | 'bPaginate': str(bool(self.activate_paginate)).lower(),
143 | 'bScrollInfinite': str(bool(self.activate_scroll_infinite)).lower(),
144 | 'bScrollCollapse': str(bool(self.activate_scroll_collapse)).lower(),
145 | 'bFilter': str(bool(self.activate_filter)).lower(),
146 | 'bLengthChange': str(bool(self.activate_length_change)).lower(),
147 | 'sScrollX': str(self.scroll_x),
148 | 'sScrollY': str(self.scroll_y),
149 | 'sPaginationType': str(self.pagination_type),
150 | 'sAjaxSource': url_for(self.source),
151 | 'aoColumnDefs': self.__json_columns_defs__()
152 | }
153 |
154 | html = '''
155 |
180 |
181 |
182 | %(columns)s
183 |
184 |
185 |
186 |
187 | ''' % data
188 | return html
189 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/static/css/style.css:
--------------------------------------------------------------------------------
1 | /* layouts
2 | */
3 |
4 | #wrapper {
5 | width: 100%;
6 | }
7 |
8 | /* global styles
9 | */
10 |
11 | a {
12 | text-decoration: none;
13 | color: #31a1ff;
14 | }
15 |
16 | a:hover {
17 | color: #99b900;
18 | }
19 |
20 | h1, h2, h3, h4, h5, h6 {
21 | font-weight: normal;
22 | }
23 |
24 | hr {
25 | border: none;
26 | background: transparent;
27 | border-bottom: 1px solid #505050;
28 | }
29 |
30 |
31 |
32 | /* styles
33 | */
34 |
35 | html, body {
36 | padding: 0px;
37 | margin: 0px;
38 | }
39 |
40 | html, body, table {
41 | font-family: Helvetica, Arial, sans-serif;
42 | color: #4d4d4d;
43 | font-size: 14px;
44 | }
45 |
46 | #wrappercolumn {
47 | min-height: 200px;
48 | width: 950px;
49 | margin: 0 auto;
50 | --background: #ffffff url('background_content.png') top left repeat-x;
51 | }
52 |
53 | /* table
54 | */
55 |
56 | table.simple {
57 | margin: 1em 0;
58 | border-collapse: collapse;
59 | width: 100%;
60 | }
61 |
62 | table.simple td,
63 | table.simple th {
64 | text-align: left;
65 | padding: 0.3em 0.4em;
66 | border-bottom: 1px solid #dddddd;
67 | }
68 |
69 | table.simple td {
70 | border-left: 1px dotted #dddddd;
71 | }
72 |
73 | table.simple th.first,
74 | table.simple td.first {
75 | border-left: none;
76 | }
77 |
78 | table.simple td.info {
79 | font-size: 10px;
80 | }
81 |
82 | .infoForm {
83 | font-size: 9px;
84 | }
85 |
86 | /* data table
87 | */
88 | .dataTables_wrapper {
89 | margin: 10px 0;
90 | }
91 |
92 | .dataTables_wrapper div,
93 | .dataTables_wrapper {
94 | clear: none !important;
95 | }
96 |
97 |
98 | .dataTables_wrapper table {
99 | width: 100%;
100 | border-collapse: collapse;
101 | }
102 | .dataTables_wrapper table td,
103 | .dataTables_wrapper table th {
104 | text-align: left;
105 | padding: 0.3em 0.4em;
106 | }
107 |
108 | .css_right {
109 | float: right;
110 | }
111 |
112 |
113 | /* login/register form
114 | */
115 | form.login,
116 | form.register {
117 | padding: 1px 10px;
118 | margin: 10px 0px;
119 | width: 350px;
120 | }
121 |
122 | form.login label,
123 | form.register label {
124 | display: block;
125 | padding: 0px;
126 | margin: 10px 0 2px 0;
127 | }
128 |
129 | form.register textarea,
130 | form.login input,
131 | form.register input {
132 | border: 1px solid #cbcbcb;
133 | padding: 5px;
134 | width: 338px;
135 | color: #606060;
136 | font-size: 16px;
137 | }
138 |
139 | form.register textarea {
140 | height: 150px;
141 | }
142 |
143 | form.login input[type='submit'],
144 | form.register input[type='submit'] {
145 | margin: 20px 0 10px 0;
146 | width: 350px;
147 | color: #404040;
148 | }
149 |
150 | form.register input[type='checkbox'] {
151 | width: auto;
152 | float: right;
153 | margin-top: -18px;
154 | }
155 |
156 | form.register input[type='radio'] {
157 | width: auto;
158 | float: right;
159 | /* margin-top: -18px; */
160 | }
161 |
162 | form.register input[readonly] {
163 | background-color: #f0f0f0;
164 | color: #909090;
165 | }
166 |
167 |
168 | /* errors
169 | */
170 |
171 | div.flash-message,
172 | div.flash-error {
173 | padding: 10px 15px;
174 | margin: 5px auto;
175 | }
176 |
177 | div.flash-error {
178 | background-color: #ff9999;
179 | border: 1px solid #aa3333;
180 | }
181 |
182 | div.flash-message {
183 | background-color: #99ff99;
184 | border: 1px solid #33aa33;
185 | }
186 |
187 | /* footer
188 | */
189 |
190 | #footer {
191 | text-align: left;
192 | padding: 1em;
193 | font-size: 0.8em;
194 | position: fixed;
195 | bottom: 0px;
196 | right: 0px;
197 | }
198 |
199 | /* content
200 | */
201 | #content {
202 | padding: 1em;
203 | font-size: 18px;
204 | }
205 |
206 | #content.nopadding {
207 | padding: 0;
208 | }
209 |
210 | #content h1 {
211 | font-size: 2.4em;
212 | font-weight: bold;
213 | padding: 0;
214 | margin: 0;
215 | }
216 |
217 | #content h1 a {
218 | color: #333;
219 | }
220 |
221 | #content h2 {
222 | color: #f8fbfd;
223 | background-color: #333;
224 | font-size: 24px;
225 | height: 33px;
226 | line-height: 33px;
227 | padding: 3px 10px;
228 | clear: both;
229 | float: left;
230 | }
231 |
232 | #content #col1,
233 | #content #col2 {
234 | width: 50%;
235 | float: left;
236 | }
237 |
238 | #content pre {
239 | font-family: monospace;
240 | margin: 0 20px;
241 | background-color: #eee;
242 | padding: 10px 15px;
243 | }
244 |
245 | #content .help {
246 | color: #aaa;
247 | }
248 |
249 | #content .button,
250 | #content input[type='submit'] {
251 | background-color: #0059f6;
252 | border-radius: 32px;
253 | padding: 10px 22px;
254 | color: #e6ebf4;
255 | font-size: 18px;
256 | }
257 |
258 | #content .button {
259 | margin: 20px auto;
260 | display: run-in;
261 | }
262 |
263 | #content .buttoncancel {
264 | background-color: #808080;
265 | }
266 |
267 | #content .logs {
268 | font-size: 12px;
269 | white-space: normal;
270 | }
271 |
272 | .clear {
273 | clear: left;
274 | }
275 |
276 | #expertdiv {
277 | display: none;
278 | }
279 |
280 | .statusbar {
281 | background-color: #808080;
282 | height: 44px;
283 | line-height: 44px;
284 | margin-right: 20px;
285 | }
286 |
287 | .progressbar {
288 | background-color: #0059f6;
289 | height: 44px;
290 | line-height: 44px;
291 | float: left;
292 | }
293 |
294 | .progressbar.failed {
295 | background-color: #f65900;
296 | }
297 |
298 | .statusmessage {
299 | height: 44px;
300 | line-height: 44px;
301 | position: absolute;
302 | width: 417px;
303 | color: white;
304 | text-align: center;
305 | }
306 |
307 | /* myaccount
308 | */
309 | #menu span.myaccount {
310 | font-size: 1em;
311 | display: block;
312 | color: #aaaaaa;
313 | }
314 |
315 | /* storage
316 | */
317 |
318 | table.browser td.first a {
319 | padding-left: 24px;
320 | background-position: center left;
321 | background-repeat: no-repeat;
322 | display: block;
323 | }
324 |
325 | td.icon-folder a {
326 | background-image: url('icon-folder.png');
327 | }
328 |
329 | td.icon-document a {
330 | background-image: url('icon-document.png');
331 | }
332 |
333 | td.icon-back a {
334 | background-image: url('icon-back.png');
335 | }
336 |
337 | /* submenu
338 | */
339 | div.submenu {
340 | margin: 1em 0;
341 | }
342 |
343 | div.submenu a {
344 | font-size: 12px;
345 | }
346 |
347 | div.submenu a:hover {
348 | color: inherit;
349 | }
350 |
351 | input[type="submit"].ui-button,
352 | a.ui-button {
353 | font-size: 12px;
354 | }
355 |
356 | /* helpers
357 | */
358 | .clear {
359 | clear: both;
360 | }
361 |
362 | /* maps
363 | */
364 | div#map_canvas {
365 | margin-top: 20px;
366 | float: left;
367 | width: 370px;
368 | height: 370px;
369 | }
370 |
371 | div#map_canvasf {
372 | width: 100%;
373 | height: 500px;
374 | }
375 |
376 | /* crosshair
377 | */
378 | div#map_crosshair {
379 | background: url("crosshair.gif") no-repeat scroll center center transparent;
380 | width: 19px;
381 | height: 19px;
382 | float: left;
383 | position: relative;
384 | left: -194px;
385 | top: 175px;
386 | margin-top: 20px;
387 | }
388 |
389 | /* buttons
390 | */
391 | a.btn-notext {
392 | width: 28px;
393 | height: 24px;
394 | }
395 |
396 | a.btn-big {
397 | line-height: 62px !important;
398 | font-size: 1.1em;
399 | margin-bottom: 1em;
400 | }
401 |
402 | a.btn-big img {
403 | border: none;
404 | vertical-align: middle;
405 | margin-right: 10px;
406 | }
407 |
408 | a.ui-button:hover {
409 | color: black;
410 | }
411 |
412 | /** Time picker
413 | */
414 | .ui-timepicker-div .ui-widget-header{ margin-bottom: 8px; }
415 | .ui-timepicker-div dl{ text-align: left; }
416 | .ui-timepicker-div dl dt{ height: 25px; }
417 | .ui-timepicker-div dl dd{ margin: -25px 10px 10px 65px; }
418 | .ui-timepicker-div td { font-size: 90%; }
419 |
--------------------------------------------------------------------------------
/slave/builder.py:
--------------------------------------------------------------------------------
1 | import re
2 | import socket
3 | from copy import copy
4 | from os import mkdir, environ
5 | from os.path import dirname, join, realpath, exists, basename
6 | from hotqueue import HotQueue
7 | from ConfigParser import ConfigParser
8 | from time import sleep
9 |
10 | buildenv = copy(environ)
11 | environ.pop('http_proxy', None)
12 |
13 | import subprocess
14 | import requests
15 |
16 | # reading the configuration
17 | hostname = socket.gethostname()
18 | config = ConfigParser()
19 | config.read(join(dirname(__file__), 'config.cfg'))
20 |
21 | # start the queue
22 | qjob = HotQueue(
23 | 'jobsubmit',
24 | host=config.get('hotqueue', 'host'),
25 | port=config.getint('hotqueue', 'port'),
26 | db=config.getint('hotqueue', 'db'),
27 | password=config.get('hotqueue', 'password'))
28 |
29 | qlog = HotQueue(
30 | 'joblog',
31 | host=config.get('hotqueue', 'host'),
32 | port=config.getint('hotqueue', 'port'),
33 | db=config.getint('hotqueue', 'db'),
34 | password=config.get('hotqueue', 'password'))
35 |
36 | # put a hello world message
37 | qlog.put({'cmd': 'alive', 'host': hostname})
38 |
39 | # create the job directory if not exist
40 | root_dir = realpath(join(dirname(__file__), 'jobs'))
41 | try:
42 | mkdir(root_dir)
43 | except:
44 | pass
45 |
46 | # checkout and clean the python-for-android project
47 | build_dir = realpath(join(dirname(__file__), 'python-for-android'))
48 | dist_dir = join(build_dir, 'dist', 'default')
49 | if not exists(build_dir):
50 | subprocess.Popen(['git', 'clone',
51 | 'git://github.com/kivy/python-for-android'], shell=False).communicate()
52 | else:
53 | subprocess.Popen(['git', 'clean', '-dxf'], shell=False,
54 | cwd=build_dir).communicate()
55 | subprocess.Popen(['git', 'pull', 'origin', 'master'], shell=False,
56 | cwd=build_dir).communicate()
57 |
58 | base_maxstatus = 10
59 | max_status = base_maxstatus
60 | id_status = 0
61 | def status(job, message):
62 | global id_status
63 | id_status += 1
64 | print job['uid'], message
65 | message = '[%d/%d] ' % (id_status, max_status) + message
66 | qlog.put({'cmd': 'status', 'uid': job['uid'], 'msg': message, 'host':
67 | hostname})
68 | qlog.put({'cmd': 'log', 'uid': job['uid'], 'line': message, 'host':
69 | hostname})
70 |
71 | def command(job, command, cwd=None):
72 | global max_status
73 | process = subprocess.Popen(command, shell=False, cwd=cwd,
74 | stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=buildenv)
75 | lines = []
76 | while process.poll() is None:
77 | line = process.stdout.readline()
78 | if not line:
79 | break
80 | line = line.rstrip('\r\n')
81 | lines.append(line)
82 | lines = lines[-100:]
83 | #qlog.put({'cmd': 'log', 'uid': job['uid'], 'line': line})
84 | print job['uid'], process.returncode, line
85 |
86 | # send progressive distribution information
87 | if 'Dependency order' in line:
88 | groups = re.search('Dependency order is (.*) \(computed\)', line)
89 | if groups:
90 | groups = groups.groups()[0].split()
91 | max_status = base_maxstatus + len(groups)
92 | if 'Run get packages' in line:
93 | status(job, 'Downloading packages')
94 | elif 'Run prebuild' in line:
95 | status(job, 'Prebuild packages')
96 | elif 'Run build' in line:
97 | status(job, 'Building packages')
98 | elif 'Run postbuild' in line:
99 | status(job, 'Postbuild packages')
100 | elif 'Run distribute' in line:
101 | status(job, 'Create distribution')
102 | elif 'Call build_' in line:
103 | groups = re.search('build_(\w+)', line)
104 | if groups:
105 | package = groups.groups()[0]
106 | status(job, 'Build ' + package)
107 |
108 | process.communicate()
109 |
110 | print process.returncode, command
111 | if process.returncode != 0:
112 | qlog.put({'cmd': 'status', 'uid': job['uid'],
113 | 'msg': 'Error while executing %r' % command,
114 | 'host': hostname})
115 | raise Exception('Error on %r.\n\n%s' % (command, '\n'.join(lines)))
116 |
117 | def builder(job_dir, **job):
118 | # got a job to do
119 | uid = job['uid']
120 |
121 | app_dir = join(job_dir, 'app')
122 | mkdir(app_dir)
123 |
124 | # do the job here
125 | url_base = config.get('www', 'url')
126 |
127 | # get data
128 | url_data = url_base + '/api/data/' + uid
129 | r = requests.get(url_data)
130 | r.raise_for_status()
131 | data_fn = join(job_dir, 'data.zip')
132 | with open(data_fn, 'wb') as fd:
133 | fd.write(r.content)
134 |
135 | # icon ?
136 | if job['have_icon'] == '1':
137 | url_icon = url_base + '/api/icon/' + uid
138 | r = requests.get(url_icon)
139 | r.raise_for_status()
140 | icon_fn = join(job_dir, 'icon.png')
141 | with open(icon_fn, 'wb') as fd:
142 | fd.write(r.content)
143 |
144 | # presplash ?
145 | if job['have_presplash'] == '1':
146 | url_presplash = url_base + '/api/presplash/' + uid
147 | r = requests.get(url_presplash)
148 | r.raise_for_status()
149 | presplash_fn = join(job_dir, 'presplash.png')
150 | with open(presplash_fn, 'wb') as fd:
151 | fd.write(r.content)
152 |
153 | # decompress
154 | command(job, ['unzip', '-d', app_dir, data_fn])
155 |
156 | # build distribution
157 | status(job, 'Build distribution')
158 | command(job, ['git', 'clean', '-dxf'], build_dir)
159 | command(job, ['./distribute.sh', '-m', job['modules']], build_dir)
160 |
161 | # build the package
162 | status(job, 'Build apk')
163 | build = ['./build.py',
164 | '--package', job['package_name'],
165 | '--name', job['package_title'],
166 | '--version', job['package_version'],
167 | '--private', app_dir,
168 | '--orientation', job['package_orientation']]
169 | if job['have_icon'] == '1':
170 | build.append('--icon')
171 | build.append(icon_fn)
172 | if job['have_presplash'] == '1':
173 | build.append('--presplash')
174 | build.append(presplash_fn)
175 | if job['package_permissions']:
176 | for permission in job['package_permissions'].split(' '):
177 | build.append('--permission')
178 | build.append(permission)
179 | build.append(job['mode'])
180 | command(job, build, dist_dir)
181 |
182 | # get apk name
183 | versioned_name = job['package_title'].replace(' ', '').replace('\'', '') + \
184 | '-' + job['package_version']
185 | apk_fn = join(dist_dir, 'bin', '%s-%s.apk' % (versioned_name,
186 | 'release-unsigned' if job['mode'] == 'release' else 'debug'))
187 |
188 | # job done, upload the apk
189 | status(job, 'Upload %s' % basename(apk_fn))
190 | with open(apk_fn, 'rb') as fd:
191 | files = {'file': (basename(apk_fn), fd)}
192 | count = 5
193 | while count:
194 | try:
195 | status(job, 'Uploading (%d remaining)' % count)
196 | r = requests.post(url_base + '/api/push/' + uid, files=files)
197 | except:
198 | count -= 1
199 | status(job, 'Upload failed (%d remaining)' % count)
200 | if not count:
201 | raise
202 | sleep(5)
203 | continue
204 |
205 | r.raise_for_status()
206 | break
207 |
208 | status(job, 'Done')
209 |
210 | # === go !
211 | print '== Ready to build!'
212 | while True:
213 | qlog.put({'cmd': 'available', 'host': hostname})
214 | task = qjob.get(timeout=5, block=True)
215 | if task is None:
216 | continue
217 | qlog.put({'cmd': 'busy', 'host': hostname})
218 | uid = task['uid']
219 | try:
220 | id_status = 0
221 | max_status = base_maxstatus
222 | status(task, 'Initialize job')
223 | # create a directory
224 | job_dir = realpath(join(root_dir, uid))
225 | mkdir(job_dir)
226 | qlog.put({'cmd': 'start', 'uid': uid, 'host': hostname})
227 | builder(job_dir, **task)
228 | qlog.put({'cmd': 'done', 'uid': uid, 'host': hostname})
229 | except Exception, e:
230 | print 'Got exception', e
231 | import traceback
232 | traceback.print_exc()
233 | qlog.put({'cmd': 'exception', 'uid': uid, 'msg': str(e), 'host':
234 | hostname})
235 | finally:
236 | # just to be entirely sure sure sure
237 | if len(uid) > 10 and not '.' in uid and job_dir.endswith(uid):
238 | subprocess.Popen(['rm', '-rf', job_dir], shell=False).communicate()
239 |
240 |
--------------------------------------------------------------------------------
/master/supervisord.conf:
--------------------------------------------------------------------------------
1 | ; Sample supervisor config file.
2 | ;
3 | ; For more information on the config file, please see:
4 | ; http://supervisord.org/configuration.html
5 | ;
6 | ; Note: shell expansion ("~" or "$HOME") is not supported. Environment
7 | ; variables can be expanded using this syntax: "%(ENV_HOME)s".
8 |
9 | [unix_http_server]
10 | file=/tmp/supervisor.sock ; (the path to the socket file)
11 | ;chmod=0700 ; socket file mode (default 0700)
12 | ;chown=nobody:nogroup ; socket file uid:gid owner
13 | ;username=user ; (default is no username (open server))
14 | ;password=123 ; (default is no password (open server))
15 |
16 | ;[inet_http_server] ; inet (TCP) server disabled by default
17 | ;port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface)
18 | ;username=user ; (default is no username (open server))
19 | ;password=123 ; (default is no password (open server))
20 |
21 | [supervisord]
22 | logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
23 | logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
24 | logfile_backups=10 ; (num of main logfile rotation backups;default 10)
25 | loglevel=info ; (log level;default info; others: debug,warn,trace)
26 | pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
27 | nodaemon=false ; (start in foreground if true;default false)
28 | minfds=1024 ; (min. avail startup file descriptors;default 1024)
29 | minprocs=200 ; (min. avail process descriptors;default 200)
30 | ;umask=022 ; (process file creation umask;default 022)
31 | ;user=chrism ; (default is current user, required if root)
32 | ;identifier=supervisor ; (supervisord identifier, default is 'supervisor')
33 | ;directory=/tmp ; (default is not to cd during start)
34 | ;nocleanup=true ; (don't clean up tempfiles at start;default false)
35 | ;childlogdir=/tmp ; ('AUTO' child log dir, default $TEMP)
36 | ;environment=KEY=value ; (key value pairs to add to environment)
37 | ;strip_ansi=false ; (strip ansi escape codes in logs; def. false)
38 |
39 | ; the below section must remain in the config file for RPC
40 | ; (supervisorctl/web interface) to work, additional interfaces may be
41 | ; added by defining them in separate rpcinterface: sections
42 | [rpcinterface:supervisor]
43 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
44 |
45 | [supervisorctl]
46 | serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
47 | ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
48 | ;username=chris ; should be same as http_username if set
49 | ;password=123 ; should be same as http_password if set
50 | ;prompt=mysupervisor ; cmd line prompt (default "supervisor")
51 | ;history_file=~/.sc_history ; use readline history if available
52 |
53 | ; The below sample program section shows all possible program subsection values,
54 | ; create one or more 'real' program: sections to be able to control them under
55 | ; supervisor.
56 |
57 | ;[program:theprogramname]
58 | ;command=/bin/cat ; the program (relative uses PATH, can take args)
59 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
60 | ;numprocs=1 ; number of processes copies to start (def 1)
61 | ;directory=/tmp ; directory to cwd to before exec (def no cwd)
62 | ;umask=022 ; umask for process (default None)
63 | ;priority=999 ; the relative start priority (default 999)
64 | ;autostart=true ; start at supervisord start (default: true)
65 | ;autorestart=unexpected ; whether/when to restart (default: unexpected)
66 | ;startsecs=1 ; number of secs prog must stay running (def. 1)
67 | ;startretries=3 ; max # of serial start failures (default 3)
68 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
69 | ;stopsignal=QUIT ; signal used to kill process (default TERM)
70 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
71 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false)
72 | ;killasgroup=false ; SIGKILL the UNIX process group (def false)
73 | ;user=chrism ; setuid to this UNIX account to run the program
74 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false)
75 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
76 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
77 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
78 | ;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
79 | ;stdout_events_enabled=false ; emit events on stdout writes (default false)
80 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
81 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
82 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10)
83 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
84 | ;stderr_events_enabled=false ; emit events on stderr writes (default false)
85 | ;environment=A=1,B=2 ; process environment additions (def no adds)
86 | ;serverurl=AUTO ; override serverurl computation (childutils)
87 |
88 | ; The below sample eventlistener section shows all possible
89 | ; eventlistener subsection values, create one or more 'real'
90 | ; eventlistener: sections to be able to handle event notifications
91 | ; sent by supervisor.
92 |
93 | ;[eventlistener:theeventlistenername]
94 | ;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
95 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
96 | ;numprocs=1 ; number of processes copies to start (def 1)
97 | ;events=EVENT ; event notif. types to subscribe to (req'd)
98 | ;buffer_size=10 ; event buffer queue size (default 10)
99 | ;directory=/tmp ; directory to cwd to before exec (def no cwd)
100 | ;umask=022 ; umask for process (default None)
101 | ;priority=-1 ; the relative start priority (default -1)
102 | ;autostart=true ; start at supervisord start (default: true)
103 | ;autorestart=unexpected ; whether/when to restart (default: unexpected)
104 | ;startsecs=1 ; number of secs prog must stay running (def. 1)
105 | ;startretries=3 ; max # of serial start failures (default 3)
106 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
107 | ;stopsignal=QUIT ; signal used to kill process (default TERM)
108 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
109 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false)
110 | ;killasgroup=false ; SIGKILL the UNIX process group (def false)
111 | ;user=chrism ; setuid to this UNIX account to run the program
112 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false)
113 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
114 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
115 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
116 | ;stdout_events_enabled=false ; emit events on stdout writes (default false)
117 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
118 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
119 | ;stderr_logfile_backups ; # of stderr logfile backups (default 10)
120 | ;stderr_events_enabled=false ; emit events on stderr writes (default false)
121 | ;environment=A=1,B=2 ; process environment additions
122 | ;serverurl=AUTO ; override serverurl computation (childutils)
123 |
124 | ; The below sample group section shows all possible group values,
125 | ; create one or more 'real' group: sections to create "heterogeneous"
126 | ; process groups.
127 |
128 | ;[group:thegroupname]
129 | ;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
130 | ;priority=999 ; the relative start priority (default 999)
131 |
132 | ; The [include] section can just contain the "files" setting. This
133 | ; setting can list multiple files (separated by whitespace or
134 | ; newlines). It can also contain wildcards. The filenames are
135 | ; interpreted as relative to this file. Included files *cannot*
136 | ; include files themselves.
137 |
138 | ;[include]
139 | ;files = relative/directory/*.ini
140 |
141 | [program:pollqlog]
142 | command=python /home/web/kivy.org/android/pollqlog.py
143 | redirect_stderr=true
144 | stdout_logfile=pollqlog.log
145 | stdout_logfile_maxbytes=20MB
146 | stdout_logfile_backups=10
147 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | Frontend module
3 | '''
4 |
5 | __all__ = ('frontend', )
6 |
7 | from flask import Blueprint, render_template, url_for, redirect, abort, \
8 | send_file, request, make_response, jsonify
9 | from web.job import read_job, read_logs, JobObj
10 | from werkzeug import secure_filename
11 | from web.config import qjob, r
12 | from os.path import exists
13 | from uuid import uuid4
14 | import subprocess
15 | import re
16 |
17 | from time import time
18 | from os import mkdir
19 | from os.path import join, splitext
20 | from flaskext.wtf import Form, TextField, SubmitField, validators, \
21 | FileField, SelectField, BooleanField
22 |
23 | frontend = Blueprint('frontend', __name__, static_folder='static',
24 | static_url_path='/frontend/static', template_folder='templates')
25 |
26 | class JobForm(Form):
27 | package_name = TextField('Package', [
28 | validators.Required(), validators.Length(min=3, max=128)],
29 | description='org.kivy.touchtracer')
30 | package_version = TextField('Version', [
31 | validators.Required(), validators.Length(min=1, max=64)],
32 | description='1.0')
33 | package_title = TextField('Name', [
34 | validators.Required(), validators.Length(min=1, max=128)],
35 | description='Touchtracer')
36 | modules = TextField('Modules',
37 | description='pil kivy')
38 | directory = FileField('Application zipped with main.py',
39 | description='''You must create a zip of your application directory
40 | containing main.py in the zip root.''')
41 | package_permissions = TextField('Permissions', [
42 | validators.Length(max=2048)],
43 | description='INTERNET')
44 | package_orientation = SelectField('Orientation', choices=[
45 | ('landscape', 'Landscape'),
46 | ('portrait', 'Portrait')], default='landscape')
47 | package_icon = FileField('Icon')
48 | package_presplash = FileField('Presplash')
49 | emails = TextField('Send notification to', [
50 | validators.Length(max=2048)],
51 | description='your@email.com')
52 | release = BooleanField('Release mode')
53 | submit = SubmitField('Submit')
54 |
55 | @frontend.route('/job/')
56 | def job(uid):
57 | job = read_job(uid)
58 | if job is None:
59 | return redirect(url_for('frontend.index'))
60 |
61 | # get the log associated to the job
62 | joblog = list(reversed(read_logs(uid)))
63 | progress = job.build_status
64 | if not progress:
65 | pprogress = 0
66 | ptotal = 1
67 | pcurrent = 0
68 | status = "Waiting a builder"
69 | else:
70 | try:
71 | progress, status = progress[1:].split(']', 1)
72 | pcurrent, ptotal = progress.split('/')
73 | except ValueError:
74 | status = job.build_status
75 | pcurrent = 1
76 | ptotal = 1
77 | pprogress = int((int(pcurrent) / float(ptotal)) * 100.)
78 | status = status.strip().rstrip('.').capitalize()
79 | return render_template('frontend/job.html', job=job, joblog=joblog,
80 | pcurrent=pcurrent, ptotal=ptotal, pprogress=pprogress,
81 | status=status)
82 |
83 | @frontend.route('/job//delete')
84 | def delete(uid):
85 | job = read_job(uid, 'package_name')
86 | if not job:
87 | abort(404)
88 |
89 | # delte job directory
90 | d = job.directory
91 | if d and len(d) > 10:
92 | subprocess.Popen(['rm', '-rf', d], shell=False).communicate()
93 |
94 | keys = r.keys('job:%s*' % uid)
95 | if keys:
96 | r.delete(*keys)
97 | keys = r.keys('log:%s*' % uid)
98 | if keys:
99 | r.delete(*keys)
100 |
101 | return redirect(url_for('frontend.index'))
102 |
103 | @frontend.route('/api/data/')
104 | def jobdata(uid):
105 | job = read_job(uid, 'directory', 'data_ext')
106 | if not job:
107 | return abort(404)
108 | print job
109 | r.set('job:%s:dt_started' % uid, time())
110 | r.set('job:%s:is_started' % uid, 1)
111 | return send_file(job.data_fn)
112 |
113 | @frontend.route('/api/icon/')
114 | def jobicon(uid):
115 | job = read_job(uid, 'directory', 'have_icon')
116 | if not job or not job.have_icon or not exists(job.icon_fn):
117 | return abort(404)
118 | return send_file(job.icon_fn)
119 |
120 | @frontend.route('/api/presplash/')
121 | def jobpresplash(uid):
122 | job = read_job(uid, 'directory')
123 | if not job or not job.have_presplash or not exists(job.presplash_fn):
124 | return abort(404)
125 | return send_file(job.presplash_fn)
126 |
127 | @frontend.route('/api/push/', methods=['POST'])
128 | def jobpush(uid):
129 | job = read_job(uid)
130 | if not job:
131 | return abort(404)
132 | file = request.files['file']
133 | if file and file.filename.rsplit('.', 1)[-1] == 'apk':
134 | filename = secure_filename(file.filename)
135 | file.save(join(job.directory, filename))
136 | r.set('job:%s:apk' % uid, filename)
137 | r.set('job:%s:dt_done' % uid, time())
138 | r.set('job:%s:is_done' % uid, 1)
139 |
140 | try:
141 | job.notify()
142 | except:
143 | pass
144 | return make_response('done')
145 | else:
146 | return abort(403)
147 |
148 | @frontend.route('/download//')
149 | def download(uid, apk):
150 | job = read_job(uid, 'apk', 'directory', 'dt_done')
151 | if not job or not job.apk or not job.dt_done:
152 | return abort(404)
153 | return send_file(job.apk_fn)
154 |
155 | @frontend.route('/')
156 | def index():
157 | form = JobForm()
158 | return render_template('frontend/index.html', form=form)
159 |
160 | @frontend.route('/faq')
161 | def faq():
162 | return render_template('frontend/faq.html')
163 |
164 | @frontend.route('/about')
165 | def about():
166 | return render_template('frontend/about.html')
167 |
168 | @frontend.route('/status')
169 | def status():
170 | key = qjob.key
171 | queue_len = qjob._HotQueue__redis.llen(key)
172 |
173 | hosts_last_alive = r.keys('host:*:last_alive')
174 | hosts = [x.split(':')[1] for x in hosts_last_alive]
175 |
176 | stats = {}
177 | for host in hosts:
178 | stats[host] = {
179 | 'last_seen': int(time() - float(r.get('host:{}:last_alive'.format(host)))),
180 | 'status': r.get('host:{}:status'.format(host))}
181 |
182 | return render_template('frontend/status.html', queue_len=queue_len,
183 | stats=stats)
184 |
185 | def csplit(s):
186 | return ' '.join([x for x in re.split(r'[.; ]', s) if len(x)])
187 |
188 | @frontend.route('/submit', methods=['POST'])
189 | def submit():
190 | form = JobForm()
191 | if form.validate_on_submit():
192 | fn = secure_filename(form.directory.file.filename)
193 | ext = splitext(fn)[-1]
194 | if splitext(fn)[-1] not in (
195 | '.zip'):#, '.tbz', '.tar.gz', '.tbz2', '.tar.bz2'):
196 | return render_template('frontend/index.html', form=form,
197 | error='Invalid application directory package')
198 |
199 | # create a job
200 | uid = str(uuid4())
201 |
202 | # fake job obj for getting path
203 | job = JobObj({'uid': uid})
204 |
205 | jobkey = 'job:%s' % uid
206 | basekey = jobkey + ':'
207 | r.set(basekey + 'dt_added', time())
208 |
209 | # create the job directory
210 | d = job.directory
211 | mkdir(d)
212 | form.directory.file.save(join(d, 'data%s' % ext))
213 |
214 | if form.package_presplash.file:
215 | form.package_presplash.file.save(job.presplash_fn)
216 | r.set(basekey + 'have_presplash', 1)
217 | else:
218 | r.set(basekey + 'have_presplash', 0)
219 |
220 | if form.package_icon.file:
221 | form.package_icon.file.save(job.icon_fn)
222 | r.set(basekey + 'have_icon', 1)
223 | else:
224 | r.set(basekey + 'have_icon', 0)
225 |
226 | # add in the database
227 | r.set(basekey + 'package_name', form.package_name.data)
228 | r.set(basekey + 'package_version', form.package_version.data)
229 | r.set(basekey + 'package_title', form.package_title.data)
230 | r.set(basekey + 'package_orientation', form.package_orientation.data)
231 | r.set(basekey + 'package_permissions', form.package_permissions.data)
232 | r.set(basekey + 'modules', form.modules.data)
233 | r.set(basekey + 'emails', form.emails.data)
234 | r.set(basekey + 'data_ext', ext)
235 | r.set(basekey + 'is_release', 1 if form.release.data else 0)
236 | r.set(basekey + 'build_status', '')
237 | r.set(basekey + 'is_failed', 0)
238 | r.set(basekey + 'is_started', 0)
239 | r.set(basekey + 'is_done', 0)
240 | r.set(basekey + 'apk', '')
241 |
242 | # creation finished
243 | r.set(jobkey, uid)
244 |
245 | # not optimized, but reread it.
246 | job = read_job(uid)
247 |
248 | # submit a job in reddis
249 | qjob.put({
250 | 'uid': job.uid,
251 | 'package_name': job.package_name,
252 | 'package_title': job.package_title,
253 | 'package_version': job.package_version,
254 | 'package_orientation': job.package_orientation,
255 | 'package_permissions': csplit(job.package_permissions),
256 | 'emails': csplit(job.emails),
257 | 'have_icon': job.have_icon,
258 | 'have_presplash': job.have_presplash,
259 | 'mode': 'release' if job.is_release == '1' else 'debug',
260 | 'modules': csplit(job.modules)
261 | })
262 |
263 | if 'batch' in request.form:
264 | d = {'status': 'ok',
265 | 'uid': job.uid,
266 | 'url': url_for('frontend.job', uid=job.uid, _external=True)}
267 | return jsonify(**d)
268 | else:
269 | # redirect to the view job
270 | return redirect(url_for('frontend.job', uid=job.uid))
271 |
272 | return render_template('frontend/index.html', form=form)
273 |
274 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jQuery UI Example Page
6 |
7 |
8 |
9 |
72 |
82 |
83 |
84 | Welcome to jQuery UI!
85 | This page demonstrates the widgets you downloaded using the theme you selected in the download builder. We've included and linked to minified versions of jQuery , your personalized copy of jQuery UI (js/jquery-ui-1.8.6.custom.min.js) , and css/smoothness/jquery-ui-1.8.6.custom.css which imports the entire jQuery UI CSS Framework. You can choose to link a subset of the CSS Framework depending on your needs.
86 | You've downloaded components and a theme that are compatible with jQuery 1.3+. Please make sure you are using jQuery 1.3+ in your production environment.
87 |
88 | YOUR COMPONENTS:
89 |
90 |
91 |
92 |
93 |
94 |
95 |
Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.
96 |
97 |
98 |
99 |
Phasellus mattis tincidunt nibh.
100 |
101 |
102 |
103 |
Nam dui erat, auctor a, dignissim quis.
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | A button element
117 |
124 |
125 |
126 |
127 |
128 |
129 |
134 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
135 |
Phasellus mattis tincidunt nibh. Cras orci urna, blandit id, pretium vel, aliquet ornare, felis. Maecenas scelerisque sem non nisl. Fusce sed lorem in enim dictum bibendum.
136 |
Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.
137 |
138 |
139 |
140 |
141 | Open Dialog
142 |
143 |
144 |
145 |
146 |
Lorem ipsum dolor sit amet, Nulla nec tortor. Donec id elit quis purus consectetur consequat.
Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci.
Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat.
Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam.
Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante.
Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi.
147 |
148 |
149 |
150 |
151 |
152 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
162 |
163 |
164 |
165 |
166 |
167 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
384 |
385 |
391 |
392 |
393 |
394 |
395 |
396 |
--------------------------------------------------------------------------------
/master/web/apps/frontend/static/css/smoothness/jquery-ui-1.8.6.custom.css:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI CSS Framework 1.8.6
3 | *
4 | * Copyright 2010, 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/Theming/API
9 | */
10 |
11 | /* Layout helpers
12 | ----------------------------------*/
13 | .ui-helper-hidden { display: none; }
14 | .ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
16 | .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
17 | .ui-helper-clearfix { display: inline-block; }
18 | /* required comment for clearfix to work in Opera \*/
19 | * html .ui-helper-clearfix { height:1%; }
20 | .ui-helper-clearfix { display:block; }
21 | /* end clearfix */
22 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
23 |
24 |
25 | /* Interaction Cues
26 | ----------------------------------*/
27 | .ui-state-disabled { cursor: default !important; }
28 |
29 |
30 | /* Icons
31 | ----------------------------------*/
32 |
33 | /* states and images */
34 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
35 |
36 |
37 | /* Misc visuals
38 | ----------------------------------*/
39 |
40 | /* Overlays */
41 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
42 |
43 |
44 | /*
45 | * jQuery UI CSS Framework 1.8.6
46 | *
47 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
48 | * Dual licensed under the MIT or GPL Version 2 licenses.
49 | * http://jquery.org/license
50 | *
51 | * http://docs.jquery.com/UI/Theming/API
52 | *
53 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
54 | */
55 |
56 |
57 | /* Component containers
58 | ----------------------------------*/
59 | .ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
60 | .ui-widget .ui-widget { font-size: 1em; }
61 | .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
62 | .ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
63 | .ui-widget-content a { color: #222222; }
64 | .ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
65 | .ui-widget-header a { color: #222222; }
66 |
67 | /* Interaction states
68 | ----------------------------------*/
69 | .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
70 | .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
71 | .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
72 | .ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
73 | .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
74 | .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
75 | .ui-widget :active { outline: none; }
76 |
77 | /* Interaction Cues
78 | ----------------------------------*/
79 | .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
80 | .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
81 | .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
82 | .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
83 | .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
84 | .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
85 | .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
86 | .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
87 |
88 | /* Icons
89 | ----------------------------------*/
90 |
91 | /* states and images */
92 | .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
93 | .ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
94 | .ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
95 | .ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
96 | .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
97 | .ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
98 | .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
99 | .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
100 |
101 | /* positioning */
102 | .ui-icon-carat-1-n { background-position: 0 0; }
103 | .ui-icon-carat-1-ne { background-position: -16px 0; }
104 | .ui-icon-carat-1-e { background-position: -32px 0; }
105 | .ui-icon-carat-1-se { background-position: -48px 0; }
106 | .ui-icon-carat-1-s { background-position: -64px 0; }
107 | .ui-icon-carat-1-sw { background-position: -80px 0; }
108 | .ui-icon-carat-1-w { background-position: -96px 0; }
109 | .ui-icon-carat-1-nw { background-position: -112px 0; }
110 | .ui-icon-carat-2-n-s { background-position: -128px 0; }
111 | .ui-icon-carat-2-e-w { background-position: -144px 0; }
112 | .ui-icon-triangle-1-n { background-position: 0 -16px; }
113 | .ui-icon-triangle-1-ne { background-position: -16px -16px; }
114 | .ui-icon-triangle-1-e { background-position: -32px -16px; }
115 | .ui-icon-triangle-1-se { background-position: -48px -16px; }
116 | .ui-icon-triangle-1-s { background-position: -64px -16px; }
117 | .ui-icon-triangle-1-sw { background-position: -80px -16px; }
118 | .ui-icon-triangle-1-w { background-position: -96px -16px; }
119 | .ui-icon-triangle-1-nw { background-position: -112px -16px; }
120 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; }
121 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; }
122 | .ui-icon-arrow-1-n { background-position: 0 -32px; }
123 | .ui-icon-arrow-1-ne { background-position: -16px -32px; }
124 | .ui-icon-arrow-1-e { background-position: -32px -32px; }
125 | .ui-icon-arrow-1-se { background-position: -48px -32px; }
126 | .ui-icon-arrow-1-s { background-position: -64px -32px; }
127 | .ui-icon-arrow-1-sw { background-position: -80px -32px; }
128 | .ui-icon-arrow-1-w { background-position: -96px -32px; }
129 | .ui-icon-arrow-1-nw { background-position: -112px -32px; }
130 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; }
131 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
132 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; }
133 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
134 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; }
135 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; }
136 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; }
137 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; }
138 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; }
139 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
140 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; }
141 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; }
142 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; }
143 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
144 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; }
145 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
146 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
147 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
148 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
149 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
150 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
151 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
152 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
153 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
154 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
155 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
156 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
157 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
158 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
159 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
160 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
161 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
162 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
163 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
164 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
165 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
166 | .ui-icon-arrow-4 { background-position: 0 -80px; }
167 | .ui-icon-arrow-4-diag { background-position: -16px -80px; }
168 | .ui-icon-extlink { background-position: -32px -80px; }
169 | .ui-icon-newwin { background-position: -48px -80px; }
170 | .ui-icon-refresh { background-position: -64px -80px; }
171 | .ui-icon-shuffle { background-position: -80px -80px; }
172 | .ui-icon-transfer-e-w { background-position: -96px -80px; }
173 | .ui-icon-transferthick-e-w { background-position: -112px -80px; }
174 | .ui-icon-folder-collapsed { background-position: 0 -96px; }
175 | .ui-icon-folder-open { background-position: -16px -96px; }
176 | .ui-icon-document { background-position: -32px -96px; }
177 | .ui-icon-document-b { background-position: -48px -96px; }
178 | .ui-icon-note { background-position: -64px -96px; }
179 | .ui-icon-mail-closed { background-position: -80px -96px; }
180 | .ui-icon-mail-open { background-position: -96px -96px; }
181 | .ui-icon-suitcase { background-position: -112px -96px; }
182 | .ui-icon-comment { background-position: -128px -96px; }
183 | .ui-icon-person { background-position: -144px -96px; }
184 | .ui-icon-print { background-position: -160px -96px; }
185 | .ui-icon-trash { background-position: -176px -96px; }
186 | .ui-icon-locked { background-position: -192px -96px; }
187 | .ui-icon-unlocked { background-position: -208px -96px; }
188 | .ui-icon-bookmark { background-position: -224px -96px; }
189 | .ui-icon-tag { background-position: -240px -96px; }
190 | .ui-icon-home { background-position: 0 -112px; }
191 | .ui-icon-flag { background-position: -16px -112px; }
192 | .ui-icon-calendar { background-position: -32px -112px; }
193 | .ui-icon-cart { background-position: -48px -112px; }
194 | .ui-icon-pencil { background-position: -64px -112px; }
195 | .ui-icon-clock { background-position: -80px -112px; }
196 | .ui-icon-disk { background-position: -96px -112px; }
197 | .ui-icon-calculator { background-position: -112px -112px; }
198 | .ui-icon-zoomin { background-position: -128px -112px; }
199 | .ui-icon-zoomout { background-position: -144px -112px; }
200 | .ui-icon-search { background-position: -160px -112px; }
201 | .ui-icon-wrench { background-position: -176px -112px; }
202 | .ui-icon-gear { background-position: -192px -112px; }
203 | .ui-icon-heart { background-position: -208px -112px; }
204 | .ui-icon-star { background-position: -224px -112px; }
205 | .ui-icon-link { background-position: -240px -112px; }
206 | .ui-icon-cancel { background-position: 0 -128px; }
207 | .ui-icon-plus { background-position: -16px -128px; }
208 | .ui-icon-plusthick { background-position: -32px -128px; }
209 | .ui-icon-minus { background-position: -48px -128px; }
210 | .ui-icon-minusthick { background-position: -64px -128px; }
211 | .ui-icon-close { background-position: -80px -128px; }
212 | .ui-icon-closethick { background-position: -96px -128px; }
213 | .ui-icon-key { background-position: -112px -128px; }
214 | .ui-icon-lightbulb { background-position: -128px -128px; }
215 | .ui-icon-scissors { background-position: -144px -128px; }
216 | .ui-icon-clipboard { background-position: -160px -128px; }
217 | .ui-icon-copy { background-position: -176px -128px; }
218 | .ui-icon-contact { background-position: -192px -128px; }
219 | .ui-icon-image { background-position: -208px -128px; }
220 | .ui-icon-video { background-position: -224px -128px; }
221 | .ui-icon-script { background-position: -240px -128px; }
222 | .ui-icon-alert { background-position: 0 -144px; }
223 | .ui-icon-info { background-position: -16px -144px; }
224 | .ui-icon-notice { background-position: -32px -144px; }
225 | .ui-icon-help { background-position: -48px -144px; }
226 | .ui-icon-check { background-position: -64px -144px; }
227 | .ui-icon-bullet { background-position: -80px -144px; }
228 | .ui-icon-radio-off { background-position: -96px -144px; }
229 | .ui-icon-radio-on { background-position: -112px -144px; }
230 | .ui-icon-pin-w { background-position: -128px -144px; }
231 | .ui-icon-pin-s { background-position: -144px -144px; }
232 | .ui-icon-play { background-position: 0 -160px; }
233 | .ui-icon-pause { background-position: -16px -160px; }
234 | .ui-icon-seek-next { background-position: -32px -160px; }
235 | .ui-icon-seek-prev { background-position: -48px -160px; }
236 | .ui-icon-seek-end { background-position: -64px -160px; }
237 | .ui-icon-seek-start { background-position: -80px -160px; }
238 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
239 | .ui-icon-seek-first { background-position: -80px -160px; }
240 | .ui-icon-stop { background-position: -96px -160px; }
241 | .ui-icon-eject { background-position: -112px -160px; }
242 | .ui-icon-volume-off { background-position: -128px -160px; }
243 | .ui-icon-volume-on { background-position: -144px -160px; }
244 | .ui-icon-power { background-position: 0 -176px; }
245 | .ui-icon-signal-diag { background-position: -16px -176px; }
246 | .ui-icon-signal { background-position: -32px -176px; }
247 | .ui-icon-battery-0 { background-position: -48px -176px; }
248 | .ui-icon-battery-1 { background-position: -64px -176px; }
249 | .ui-icon-battery-2 { background-position: -80px -176px; }
250 | .ui-icon-battery-3 { background-position: -96px -176px; }
251 | .ui-icon-circle-plus { background-position: 0 -192px; }
252 | .ui-icon-circle-minus { background-position: -16px -192px; }
253 | .ui-icon-circle-close { background-position: -32px -192px; }
254 | .ui-icon-circle-triangle-e { background-position: -48px -192px; }
255 | .ui-icon-circle-triangle-s { background-position: -64px -192px; }
256 | .ui-icon-circle-triangle-w { background-position: -80px -192px; }
257 | .ui-icon-circle-triangle-n { background-position: -96px -192px; }
258 | .ui-icon-circle-arrow-e { background-position: -112px -192px; }
259 | .ui-icon-circle-arrow-s { background-position: -128px -192px; }
260 | .ui-icon-circle-arrow-w { background-position: -144px -192px; }
261 | .ui-icon-circle-arrow-n { background-position: -160px -192px; }
262 | .ui-icon-circle-zoomin { background-position: -176px -192px; }
263 | .ui-icon-circle-zoomout { background-position: -192px -192px; }
264 | .ui-icon-circle-check { background-position: -208px -192px; }
265 | .ui-icon-circlesmall-plus { background-position: 0 -208px; }
266 | .ui-icon-circlesmall-minus { background-position: -16px -208px; }
267 | .ui-icon-circlesmall-close { background-position: -32px -208px; }
268 | .ui-icon-squaresmall-plus { background-position: -48px -208px; }
269 | .ui-icon-squaresmall-minus { background-position: -64px -208px; }
270 | .ui-icon-squaresmall-close { background-position: -80px -208px; }
271 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
272 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
273 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; }
274 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
275 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
276 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; }
277 |
278 |
279 | /* Misc visuals
280 | ----------------------------------*/
281 |
282 | /* Corner radius */
283 | .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
284 | .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
285 | .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
286 | .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
287 | .ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
288 | .ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
289 | .ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
290 | .ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
291 | .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
292 |
293 | /* Overlays */
294 | .ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
295 | .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*
296 | * jQuery UI Resizable 1.8.6
297 | *
298 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
299 | * Dual licensed under the MIT or GPL Version 2 licenses.
300 | * http://jquery.org/license
301 | *
302 | * http://docs.jquery.com/UI/Resizable#theming
303 | */
304 | .ui-resizable { position: relative;}
305 | .ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
306 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
307 | .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
308 | .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
309 | .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
310 | .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
311 | .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
312 | .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
313 | .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
314 | .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
315 | * jQuery UI Selectable 1.8.6
316 | *
317 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
318 | * Dual licensed under the MIT or GPL Version 2 licenses.
319 | * http://jquery.org/license
320 | *
321 | * http://docs.jquery.com/UI/Selectable#theming
322 | */
323 | .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
324 | /*
325 | * jQuery UI Accordion 1.8.6
326 | *
327 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
328 | * Dual licensed under the MIT or GPL Version 2 licenses.
329 | * http://jquery.org/license
330 | *
331 | * http://docs.jquery.com/UI/Accordion#theming
332 | */
333 | /* IE/Win - Fix animation bug - #4615 */
334 | .ui-accordion { width: 100%; }
335 | .ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
336 | .ui-accordion .ui-accordion-li-fix { display: inline; }
337 | .ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
338 | .ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
339 | .ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
340 | .ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
341 | .ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
342 | .ui-accordion .ui-accordion-content-active { display: block; }/*
343 | * jQuery UI Autocomplete 1.8.6
344 | *
345 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
346 | * Dual licensed under the MIT or GPL Version 2 licenses.
347 | * http://jquery.org/license
348 | *
349 | * http://docs.jquery.com/UI/Autocomplete#theming
350 | */
351 | .ui-autocomplete { position: absolute; cursor: default; }
352 |
353 | /* workarounds */
354 | * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
355 |
356 | /*
357 | * jQuery UI Menu 1.8.6
358 | *
359 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
360 | * Dual licensed under the MIT or GPL Version 2 licenses.
361 | * http://jquery.org/license
362 | *
363 | * http://docs.jquery.com/UI/Menu#theming
364 | */
365 | .ui-menu {
366 | list-style:none;
367 | padding: 2px;
368 | margin: 0;
369 | display:block;
370 | float: left;
371 | }
372 | .ui-menu .ui-menu {
373 | margin-top: -3px;
374 | }
375 | .ui-menu .ui-menu-item {
376 | margin:0;
377 | padding: 0;
378 | zoom: 1;
379 | float: left;
380 | clear: left;
381 | width: 100%;
382 | }
383 | .ui-menu .ui-menu-item a {
384 | text-decoration:none;
385 | display:block;
386 | padding:.2em .4em;
387 | line-height:1.5;
388 | zoom:1;
389 | }
390 | .ui-menu .ui-menu-item a.ui-state-hover,
391 | .ui-menu .ui-menu-item a.ui-state-active {
392 | font-weight: normal;
393 | margin: -1px;
394 | }
395 | /*
396 | * jQuery UI Button 1.8.6
397 | *
398 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
399 | * Dual licensed under the MIT or GPL Version 2 licenses.
400 | * http://jquery.org/license
401 | *
402 | * http://docs.jquery.com/UI/Button#theming
403 | */
404 | .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
405 | .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
406 | button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
407 | .ui-button-icons-only { width: 3.4em; }
408 | button.ui-button-icons-only { width: 3.7em; }
409 |
410 | /*button text element */
411 | .ui-button .ui-button-text { display: block; line-height: 1.4; }
412 | .ui-button-text-only .ui-button-text { padding: .4em 1em; }
413 | .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
414 | .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
415 | .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
416 | .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
417 | /* no icon support for input elements, provide padding by default */
418 | input.ui-button { padding: .4em 1em; }
419 |
420 | /*button icon element(s) */
421 | .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
422 | .ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
423 | .ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
424 | .ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
425 | .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
426 |
427 | /*button sets*/
428 | .ui-buttonset { margin-right: 7px; }
429 | .ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
430 |
431 | /* workarounds */
432 | button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
433 | /*
434 | * jQuery UI Dialog 1.8.6
435 | *
436 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
437 | * Dual licensed under the MIT or GPL Version 2 licenses.
438 | * http://jquery.org/license
439 | *
440 | * http://docs.jquery.com/UI/Dialog#theming
441 | */
442 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
443 | .ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; }
444 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; }
445 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
446 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
447 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
448 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
449 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
450 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
451 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
452 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
453 | .ui-draggable .ui-dialog-titlebar { cursor: move; }
454 | /*
455 | * jQuery UI Slider 1.8.6
456 | *
457 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
458 | * Dual licensed under the MIT or GPL Version 2 licenses.
459 | * http://jquery.org/license
460 | *
461 | * http://docs.jquery.com/UI/Slider#theming
462 | */
463 | .ui-slider { position: relative; text-align: left; }
464 | .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
465 | .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
466 |
467 | .ui-slider-horizontal { height: .8em; }
468 | .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
469 | .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
470 | .ui-slider-horizontal .ui-slider-range-min { left: 0; }
471 | .ui-slider-horizontal .ui-slider-range-max { right: 0; }
472 |
473 | .ui-slider-vertical { width: .8em; height: 100px; }
474 | .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
475 | .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
476 | .ui-slider-vertical .ui-slider-range-min { bottom: 0; }
477 | .ui-slider-vertical .ui-slider-range-max { top: 0; }/*
478 | * jQuery UI Tabs 1.8.6
479 | *
480 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
481 | * Dual licensed under the MIT or GPL Version 2 licenses.
482 | * http://jquery.org/license
483 | *
484 | * http://docs.jquery.com/UI/Tabs#theming
485 | */
486 | .ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
487 | .ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
488 | .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
489 | .ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
490 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
491 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
492 | .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
493 | .ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
494 | .ui-tabs .ui-tabs-hide { display: none !important; }
495 | /*
496 | * jQuery UI Datepicker 1.8.6
497 | *
498 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
499 | * Dual licensed under the MIT or GPL Version 2 licenses.
500 | * http://jquery.org/license
501 | *
502 | * http://docs.jquery.com/UI/Datepicker#theming
503 | */
504 | .ui-datepicker { width: 17em; padding: .2em .2em 0; }
505 | .ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
506 | .ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
507 | .ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
508 | .ui-datepicker .ui-datepicker-prev { left:2px; }
509 | .ui-datepicker .ui-datepicker-next { right:2px; }
510 | .ui-datepicker .ui-datepicker-prev-hover { left:1px; }
511 | .ui-datepicker .ui-datepicker-next-hover { right:1px; }
512 | .ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
513 | .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
514 | .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
515 | .ui-datepicker select.ui-datepicker-month-year {width: 100%;}
516 | .ui-datepicker select.ui-datepicker-month,
517 | .ui-datepicker select.ui-datepicker-year { width: 49%;}
518 | .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
519 | .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
520 | .ui-datepicker td { border: 0; padding: 1px; }
521 | .ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
522 | .ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
523 | .ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
524 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
525 |
526 | /* with multiple calendars */
527 | .ui-datepicker.ui-datepicker-multi { width:auto; }
528 | .ui-datepicker-multi .ui-datepicker-group { float:left; }
529 | .ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
530 | .ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
531 | .ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
532 | .ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
533 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
534 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
535 | .ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
536 | .ui-datepicker-row-break { clear:both; width:100%; }
537 |
538 | /* RTL support */
539 | .ui-datepicker-rtl { direction: rtl; }
540 | .ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
541 | .ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
542 | .ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
543 | .ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
544 | .ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
545 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
546 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
547 | .ui-datepicker-rtl .ui-datepicker-group { float:right; }
548 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
549 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
550 |
551 | /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
552 | .ui-datepicker-cover {
553 | display: none; /*sorry for IE5*/
554 | display/**/: block; /*sorry for IE5*/
555 | position: absolute; /*must have*/
556 | z-index: -1; /*must have*/
557 | filter: mask(); /*must have*/
558 | top: -4px; /*must have*/
559 | left: -4px; /*must have*/
560 | width: 200px; /*must have*/
561 | height: 200px; /*must have*/
562 | }/*
563 | * jQuery UI Progressbar 1.8.6
564 | *
565 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
566 | * Dual licensed under the MIT or GPL Version 2 licenses.
567 | * http://jquery.org/license
568 | *
569 | * http://docs.jquery.com/UI/Progressbar#theming
570 | */
571 | .ui-progressbar { height:2em; text-align: left; }
572 | .ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
--------------------------------------------------------------------------------
/master/web/apps/frontend/static/js/jquery-ui-timepicker-addon.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery timepicker addon
3 | * By: Trent Richardson [http://trentrichardson.com]
4 | * Version 0.9.6
5 | * Last Modified: 07/20/2011
6 | *
7 | * Copyright 2011 Trent Richardson
8 | * Dual licensed under the MIT and GPL licenses.
9 | * http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
10 | * http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
11 | *
12 | * HERES THE CSS:
13 | * .ui-timepicker-div .ui-widget-header{ margin-bottom: 8px; }
14 | * .ui-timepicker-div dl{ text-align: left; }
15 | * .ui-timepicker-div dl dt{ height: 25px; }
16 | * .ui-timepicker-div dl dd{ margin: -25px 10px 10px 65px; }
17 | * .ui-timepicker-div td { font-size: 90%; }
18 | */
19 |
20 | (function($) {
21 |
22 | $.extend($.ui, { timepicker: { version: "0.9.6" } });
23 |
24 | /* Time picker manager.
25 | Use the singleton instance of this class, $.timepicker, to interact with the time picker.
26 | Settings for (groups of) time pickers are maintained in an instance object,
27 | allowing multiple different settings on the same page. */
28 |
29 | function Timepicker() {
30 | this.regional = []; // Available regional settings, indexed by language code
31 | this.regional[''] = { // Default regional settings
32 | currentText: 'Now',
33 | closeText: 'Done',
34 | ampm: false,
35 | timeFormat: 'hh:mm tt',
36 | timeSuffix: '',
37 | timeOnlyTitle: 'Choose Time',
38 | timeText: 'Time',
39 | hourText: 'Hour',
40 | minuteText: 'Minute',
41 | secondText: 'Second',
42 | timezoneText: 'Time Zone'
43 | };
44 | this._defaults = { // Global defaults for all the datetime picker instances
45 | showButtonPanel: true,
46 | timeOnly: false,
47 | showHour: true,
48 | showMinute: true,
49 | showSecond: false,
50 | showTimezone: false,
51 | showTime: true,
52 | stepHour: 0.05,
53 | stepMinute: 0.05,
54 | stepSecond: 0.05,
55 | hour: 0,
56 | minute: 0,
57 | second: 0,
58 | timezone: '+0000',
59 | hourMin: 0,
60 | minuteMin: 0,
61 | secondMin: 0,
62 | hourMax: 23,
63 | minuteMax: 59,
64 | secondMax: 59,
65 | minDateTime: null,
66 | maxDateTime: null,
67 | hourGrid: 0,
68 | minuteGrid: 0,
69 | secondGrid: 0,
70 | alwaysSetTime: true,
71 | separator: ' ',
72 | altFieldTimeOnly: true,
73 | showTimepicker: true,
74 | timezoneList: ["-1100", "-1000", "-0900", "-0800", "-0700", "-0600",
75 | "-0500", "-0400", "-0300", "-0200", "-0100", "+0000",
76 | "+0100", "+0200", "+0300", "+0400", "+0500", "+0600",
77 | "+0700", "+0800", "+0900", "+1000", "+1100", "+1200"]
78 | };
79 | $.extend(this._defaults, this.regional['']);
80 | }
81 |
82 | $.extend(Timepicker.prototype, {
83 | $input: null,
84 | $altInput: null,
85 | $timeObj: null,
86 | inst: null,
87 | hour_slider: null,
88 | minute_slider: null,
89 | second_slider: null,
90 | timezone_select: null,
91 | hour: 0,
92 | minute: 0,
93 | second: 0,
94 | timezone: '+0000',
95 | hourMinOriginal: null,
96 | minuteMinOriginal: null,
97 | secondMinOriginal: null,
98 | hourMaxOriginal: null,
99 | minuteMaxOriginal: null,
100 | secondMaxOriginal: null,
101 | ampm: '',
102 | formattedDate: '',
103 | formattedTime: '',
104 | formattedDateTime: '',
105 | timezoneList: ["-1100", "-1000", "-0900", "-0800", "-0700", "-0600",
106 | "-0500", "-0400", "-0300", "-0200", "-0100", "+0000",
107 | "+0100", "+0200", "+0300", "+0400", "+0500", "+0600",
108 | "+0700", "+0800", "+0900", "+1000", "+1100", "+1200"],
109 |
110 | /* Override the default settings for all instances of the time picker.
111 | @param settings object - the new settings to use as defaults (anonymous object)
112 | @return the manager object */
113 | setDefaults: function(settings) {
114 | extendRemove(this._defaults, settings || {});
115 | return this;
116 | },
117 |
118 | //########################################################################
119 | // Create a new Timepicker instance
120 | //########################################################################
121 | _newInst: function($input, o) {
122 | var tp_inst = new Timepicker(),
123 | inlineSettings = {};
124 |
125 | for (var attrName in this._defaults) {
126 | var attrValue = $input.attr('time:' + attrName);
127 | if (attrValue) {
128 | try {
129 | inlineSettings[attrName] = eval(attrValue);
130 | } catch (err) {
131 | inlineSettings[attrName] = attrValue;
132 | }
133 | }
134 | }
135 | tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, {
136 | beforeShow: function(input, dp_inst) {
137 | if ($.isFunction(o.beforeShow))
138 | o.beforeShow(input, dp_inst, tp_inst);
139 | },
140 | onChangeMonthYear: function(year, month, dp_inst) {
141 | // Update the time as well : this prevents the time from disappearing from the $input field.
142 | tp_inst._updateDateTime(dp_inst);
143 | if ($.isFunction(o.onChangeMonthYear))
144 | o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
145 | },
146 | onClose: function(dateText, dp_inst) {
147 | if (tp_inst.timeDefined === true && $input.val() != '')
148 | tp_inst._updateDateTime(dp_inst);
149 | if ($.isFunction(o.onClose))
150 | o.onClose.call($input[0], dateText, dp_inst, tp_inst);
151 | },
152 | timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
153 | });
154 |
155 | tp_inst.hour = tp_inst._defaults.hour;
156 | tp_inst.minute = tp_inst._defaults.minute;
157 | tp_inst.second = tp_inst._defaults.second;
158 | tp_inst.ampm = '';
159 | tp_inst.$input = $input;
160 |
161 | if (o.altField)
162 | tp_inst.$altInput = $(o.altField)
163 | .css({ cursor: 'pointer' })
164 | .focus(function(){ $input.trigger("focus"); });
165 |
166 | // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
167 | if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date)
168 | tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
169 | if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date)
170 | tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
171 | if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date)
172 | tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
173 | if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date)
174 | tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
175 |
176 | return tp_inst;
177 | },
178 |
179 | //########################################################################
180 | // add our sliders to the calendar
181 | //########################################################################
182 | _addTimePicker: function(dp_inst) {
183 | var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ?
184 | this.$input.val() + ' ' + this.$altInput.val() :
185 | this.$input.val();
186 |
187 | this.timeDefined = this._parseTime(currDT);
188 | this._limitMinMaxDateTime(dp_inst, false);
189 | this._injectTimePicker();
190 | },
191 |
192 | //########################################################################
193 | // parse the time string from input value or _setTime
194 | //########################################################################
195 | _parseTime: function(timeString, withDate) {
196 | var regstr = this._defaults.timeFormat.toString()
197 | .replace(/h{1,2}/ig, '(\\d?\\d)')
198 | .replace(/m{1,2}/ig, '(\\d?\\d)')
199 | .replace(/s{1,2}/ig, '(\\d?\\d)')
200 | .replace(/t{1,2}/ig, '(am|pm|a|p)?')
201 | .replace(/z{1}/ig, '((\\+|-)\\d\\d\\d\\d)?')
202 | .replace(/\s/g, '\\s?') + this._defaults.timeSuffix + '$',
203 | order = this._getFormatPositions(),
204 | treg;
205 |
206 | if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]);
207 |
208 | if (withDate || !this._defaults.timeOnly) {
209 | // the time should come after x number of characters and a space.
210 | // x = at least the length of text specified by the date format
211 | var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
212 | // escape special regex characters in the seperator
213 | var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g");
214 | regstr = '.{' + dp_dateFormat.length + ',}' + this._defaults.separator.replace(specials, "\\$&") + regstr;
215 | }
216 |
217 | treg = timeString.match(new RegExp(regstr, 'i'));
218 |
219 | if (treg) {
220 | if (order.t !== -1)
221 | this.ampm = ((treg[order.t] === undefined || treg[order.t].length === 0) ?
222 | '' :
223 | (treg[order.t].charAt(0).toUpperCase() == 'A') ? 'AM' : 'PM').toUpperCase();
224 |
225 | if (order.h !== -1) {
226 | if (this.ampm == 'AM' && treg[order.h] == '12')
227 | this.hour = 0; // 12am = 0 hour
228 | else if (this.ampm == 'PM' && treg[order.h] != '12')
229 | this.hour = (parseFloat(treg[order.h]) + 12).toFixed(0); // 12pm = 12 hour, any other pm = hour + 12
230 | else this.hour = Number(treg[order.h]);
231 | }
232 |
233 | if (order.m !== -1) this.minute = Number(treg[order.m]);
234 | if (order.s !== -1) this.second = Number(treg[order.s]);
235 | if (order.z !== -1) this.timezone = treg[order.z];
236 |
237 | return true;
238 |
239 | }
240 | return false;
241 | },
242 |
243 | //########################################################################
244 | // figure out position of time elements.. cause js cant do named captures
245 | //########################################################################
246 | _getFormatPositions: function() {
247 | var finds = this._defaults.timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|t{1,2}|z)/g),
248 | orders = { h: -1, m: -1, s: -1, t: -1, z: -1 };
249 |
250 | if (finds)
251 | for (var i = 0; i < finds.length; i++)
252 | if (orders[finds[i].toString().charAt(0)] == -1)
253 | orders[finds[i].toString().charAt(0)] = i + 1;
254 |
255 | return orders;
256 | },
257 |
258 | //########################################################################
259 | // generate and inject html for timepicker into ui datepicker
260 | //########################################################################
261 | _injectTimePicker: function() {
262 | var $dp = this.inst.dpDiv,
263 | o = this._defaults,
264 | tp_inst = this,
265 | // Added by Peter Medeiros:
266 | // - Figure out what the hour/minute/second max should be based on the step values.
267 | // - Example: if stepMinute is 15, then minMax is 45.
268 | hourMax = (o.hourMax - (o.hourMax % o.stepHour)).toFixed(0),
269 | minMax = (o.minuteMax - (o.minuteMax % o.stepMinute)).toFixed(0),
270 | secMax = (o.secondMax - (o.secondMax % o.stepSecond)).toFixed(0),
271 | dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, '');
272 |
273 | // Prevent displaying twice
274 | //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) {
275 | if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) {
276 | var noDisplay = ' style="display:none;"',
277 | html = '' +
278 | '' + o.timeText + ' ' +
280 | ' ' +
282 | '' + o.hourText + ' ',
284 | hourGridSize = 0,
285 | minuteGridSize = 0,
286 | secondGridSize = 0,
287 | size;
288 |
289 | if (o.showHour && o.hourGrid > 0) {
290 | html += '' +
291 | '
' +
292 | '';
293 |
294 | for (var h = o.hourMin; h <= hourMax; h += o.hourGrid) {
295 | hourGridSize++;
296 | var tmph = (o.ampm && h > 12) ? h-12 : h;
297 | if (tmph < 10) tmph = '0' + tmph;
298 | if (o.ampm) {
299 | if (h == 0) tmph = 12 +'a';
300 | else if (h < 12) tmph += 'a';
301 | else tmph += 'p';
302 | }
303 | html += '' + tmph + ' ';
304 | }
305 |
306 | html += '
' +
307 | ' ';
308 | } else html += ' ';
310 |
311 | html += '' + o.minuteText + ' ';
313 |
314 | if (o.showMinute && o.minuteGrid > 0) {
315 | html += '' +
316 | '
' +
318 | '';
319 |
320 | for (var m = o.minuteMin; m <= minMax; m += o.minuteGrid) {
321 | minuteGridSize++;
322 | html += '' + ((m < 10) ? '0' : '') + m + ' ';
323 | }
324 |
325 | html += '
' +
326 | ' ';
327 | } else html += ' ';
329 |
330 | html += '' + o.secondText + ' ';
332 |
333 | if (o.showSecond && o.secondGrid > 0) {
334 | html += '' +
335 | '
' +
337 | '';
338 |
339 | for (var s = o.secondMin; s <= secMax; s += o.secondGrid) {
340 | secondGridSize++;
341 | html += '' + ((s < 10) ? '0' : '') + s + ' ';
342 | }
343 |
344 | html += '
' +
345 | ' ';
346 | } else html += ' ';
348 |
349 | html += '' + o.timezoneText + ' ';
351 | html += ' ';
353 |
354 | html += ' ';
355 | $tp = $(html);
356 |
357 | // if we only want time picker...
358 | if (o.timeOnly === true) {
359 | $tp.prepend(
360 | '');
363 | $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
364 | }
365 |
366 | this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({
367 | orientation: "horizontal",
368 | value: this.hour,
369 | min: o.hourMin,
370 | max: hourMax,
371 | step: o.stepHour,
372 | slide: function(event, ui) {
373 | tp_inst.hour_slider.slider( "option", "value", ui.value);
374 | tp_inst._onTimeChange();
375 | }
376 | });
377 |
378 | // Updated by Peter Medeiros:
379 | // - Pass in Event and UI instance into slide function
380 | this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({
381 | orientation: "horizontal",
382 | value: this.minute,
383 | min: o.minuteMin,
384 | max: minMax,
385 | step: o.stepMinute,
386 | slide: function(event, ui) {
387 | // update the global minute slider instance value with the current slider value
388 | tp_inst.minute_slider.slider( "option", "value", ui.value);
389 | tp_inst._onTimeChange();
390 | }
391 | });
392 |
393 | this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({
394 | orientation: "horizontal",
395 | value: this.second,
396 | min: o.secondMin,
397 | max: secMax,
398 | step: o.stepSecond,
399 | slide: function(event, ui) {
400 | tp_inst.second_slider.slider( "option", "value", ui.value);
401 | tp_inst._onTimeChange();
402 | }
403 | });
404 |
405 |
406 | this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append(' ').find("select");
407 | $.fn.append.apply(this.timezone_select,
408 | $.map(o.timezoneList, function(val, idx) {
409 | return $(" ")
410 | .val(typeof val == "object" ? val.value : val)
411 | .text(typeof val == "object" ? val.label : val);
412 | })
413 | );
414 | this.timezone_select.val((typeof this.timezone != "undefined" && this.timezone != null && this.timezone != "") ? this.timezone : o.timezone);
415 | this.timezone_select.change(function() {
416 | tp_inst._onTimeChange();
417 | });
418 |
419 | // Add grid functionality
420 | if (o.showHour && o.hourGrid > 0) {
421 | size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin);
422 |
423 | $tp.find(".ui_tpicker_hour table").css({
424 | width: size + "%",
425 | marginLeft: (size / (-2 * hourGridSize)) + "%",
426 | borderCollapse: 'collapse'
427 | }).find("td").each( function(index) {
428 | $(this).click(function() {
429 | var h = $(this).html();
430 | if(o.ampm) {
431 | var ap = h.substring(2).toLowerCase(),
432 | aph = parseInt(h.substring(0,2), 10);
433 | if (ap == 'a') {
434 | if (aph == 12) h = 0;
435 | else h = aph;
436 | } else if (aph == 12) h = 12;
437 | else h = aph + 12;
438 | }
439 | tp_inst.hour_slider.slider("option", "value", h);
440 | tp_inst._onTimeChange();
441 | tp_inst._onSelectHandler();
442 | }).css({
443 | cursor: 'pointer',
444 | width: (100 / hourGridSize) + '%',
445 | textAlign: 'center',
446 | overflow: 'hidden'
447 | });
448 | });
449 | }
450 |
451 | if (o.showMinute && o.minuteGrid > 0) {
452 | size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin);
453 | $tp.find(".ui_tpicker_minute table").css({
454 | width: size + "%",
455 | marginLeft: (size / (-2 * minuteGridSize)) + "%",
456 | borderCollapse: 'collapse'
457 | }).find("td").each(function(index) {
458 | $(this).click(function() {
459 | tp_inst.minute_slider.slider("option", "value", $(this).html());
460 | tp_inst._onTimeChange();
461 | tp_inst._onSelectHandler();
462 | }).css({
463 | cursor: 'pointer',
464 | width: (100 / minuteGridSize) + '%',
465 | textAlign: 'center',
466 | overflow: 'hidden'
467 | });
468 | });
469 | }
470 |
471 | if (o.showSecond && o.secondGrid > 0) {
472 | $tp.find(".ui_tpicker_second table").css({
473 | width: size + "%",
474 | marginLeft: (size / (-2 * secondGridSize)) + "%",
475 | borderCollapse: 'collapse'
476 | }).find("td").each(function(index) {
477 | $(this).click(function() {
478 | tp_inst.second_slider.slider("option", "value", $(this).html());
479 | tp_inst._onTimeChange();
480 | tp_inst._onSelectHandler();
481 | }).css({
482 | cursor: 'pointer',
483 | width: (100 / secondGridSize) + '%',
484 | textAlign: 'center',
485 | overflow: 'hidden'
486 | });
487 | });
488 | }
489 |
490 | var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
491 | if ($buttonPanel.length) $buttonPanel.before($tp);
492 | else $dp.append($tp);
493 |
494 | this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id);
495 |
496 | if (this.inst !== null) {
497 | var timeDefined = this.timeDefined;
498 | this._onTimeChange();
499 | this.timeDefined = timeDefined;
500 | }
501 |
502 | //Emulate datepicker onSelect behavior. Call on slidestop.
503 | var onSelectDelegate = function() {
504 | tp_inst._onSelectHandler();
505 | };
506 | this.hour_slider.bind('slidestop',onSelectDelegate);
507 | this.minute_slider.bind('slidestop',onSelectDelegate);
508 | this.second_slider.bind('slidestop',onSelectDelegate);
509 | }
510 | },
511 |
512 | //########################################################################
513 | // This function tries to limit the ability to go outside the
514 | // min/max date range
515 | //########################################################################
516 | _limitMinMaxDateTime: function(dp_inst, adjustSliders){
517 | var o = this._defaults,
518 | dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
519 |
520 | if(!this._defaults.showTimepicker) return; // No time so nothing to check here
521 |
522 | if($.datepicker._get(dp_inst, 'minDateTime') !== null && dp_date){
523 | var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
524 | minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
525 |
526 | if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null){
527 | this.hourMinOriginal = o.hourMin;
528 | this.minuteMinOriginal = o.minuteMin;
529 | this.secondMinOriginal = o.secondMin;
530 | }
531 |
532 | if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) {
533 | this._defaults.hourMin = minDateTime.getHours();
534 | if (this.hour <= this._defaults.hourMin) {
535 | this.hour = this._defaults.hourMin;
536 | this._defaults.minuteMin = minDateTime.getMinutes();
537 | if (this.minute <= this._defaults.minuteMin) {
538 | this.minute = this._defaults.minuteMin;
539 | this._defaults.secondMin = minDateTime.getSeconds();
540 | } else {
541 | if(this.second < this._defaults.secondMin) this.second = this._defaults.secondMin;
542 | this._defaults.secondMin = this.secondMinOriginal;
543 | }
544 | } else {
545 | this._defaults.minuteMin = this.minuteMinOriginal;
546 | this._defaults.secondMin = this.secondMinOriginal;
547 | }
548 | }else{
549 | this._defaults.hourMin = this.hourMinOriginal;
550 | this._defaults.minuteMin = this.minuteMinOriginal;
551 | this._defaults.secondMin = this.secondMinOriginal;
552 | }
553 | }
554 |
555 | if($.datepicker._get(dp_inst, 'maxDateTime') !== null && dp_date){
556 | var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
557 | maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
558 |
559 | if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){
560 | this.hourMaxOriginal = o.hourMax;
561 | this.minuteMaxOriginal = o.minuteMax;
562 | this.secondMaxOriginal = o.secondMax;
563 | }
564 |
565 | if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){
566 | this._defaults.hourMax = maxDateTime.getHours();
567 | if (this.hour >= this._defaults.hourMax) {
568 | this.hour = this._defaults.hourMax;
569 | this._defaults.minuteMax = maxDateTime.getMinutes();
570 | if (this.minute >= this._defaults.minuteMax) {
571 | this.minute = this._defaults.minuteMax;
572 | this._defaults.secondMax = maxDateTime.getSeconds();
573 | } else {
574 | if(this.second > this._defaults.secondMax) this.second = this._defaults.secondMax;
575 | this._defaults.secondMax = this.secondMaxOriginal;
576 | }
577 | } else {
578 | this._defaults.minuteMax = this.minuteMaxOriginal;
579 | this._defaults.secondMax = this.secondMaxOriginal;
580 | }
581 | }else{
582 | this._defaults.hourMax = this.hourMaxOriginal;
583 | this._defaults.minuteMax = this.minuteMaxOriginal;
584 | this._defaults.secondMax = this.secondMaxOriginal;
585 | }
586 | }
587 |
588 | if(adjustSliders !== undefined && adjustSliders === true){
589 | var hourMax = (this._defaults.hourMax - (this._defaults.hourMax % this._defaults.stepHour)).toFixed(0),
590 | minMax = (this._defaults.minuteMax - (this._defaults.minuteMax % this._defaults.stepMinute)).toFixed(0),
591 | secMax = (this._defaults.secondMax - (this._defaults.secondMax % this._defaults.stepSecond)).toFixed(0);
592 |
593 | if(this.hour_slider)
594 | this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour);
595 | if(this.minute_slider)
596 | this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute);
597 | if(this.second_slider)
598 | this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second);
599 | }
600 |
601 | },
602 |
603 |
604 | //########################################################################
605 | // when a slider moves, set the internal time...
606 | // on time change is also called when the time is updated in the text field
607 | //########################################################################
608 | _onTimeChange: function() {
609 | var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false,
610 | minute = (this.minute_slider) ? this.minute_slider.slider('value') : false,
611 | second = (this.second_slider) ? this.second_slider.slider('value') : false,
612 | timezone = (this.timezone_select) ? this.timezone_select.val() : false;
613 |
614 | if (typeof(hour) == 'object') hour = false;
615 | if (typeof(minute) == 'object') minute = false;
616 | if (typeof(second) == 'object') second = false;
617 | if (typeof(timezone) == 'object') timezone = false;
618 |
619 | if (hour !== false) hour = parseInt(hour,10);
620 | if (minute !== false) minute = parseInt(minute,10);
621 | if (second !== false) second = parseInt(second,10);
622 |
623 | var ampm = (hour < 12) ? 'AM' : 'PM';
624 |
625 | // If the update was done in the input field, the input field should not be updated.
626 | // If the update was done using the sliders, update the input field.
627 | var hasChanged = (hour != this.hour || minute != this.minute || second != this.second || (this.ampm.length > 0 && this.ampm != ampm) || timezone != this.timezone);
628 |
629 | if (hasChanged) {
630 |
631 | if (hour !== false)this.hour = hour;
632 | if (minute !== false) this.minute = minute;
633 | if (second !== false) this.second = second;
634 | if (timezone !== false) this.timezone = timezone;
635 |
636 | if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]);
637 |
638 | this._limitMinMaxDateTime(this.inst, true);
639 | }
640 | if (this._defaults.ampm) this.ampm = ampm;
641 |
642 | this._formatTime();
643 | if (this.$timeObj) this.$timeObj.text(this.formattedTime + this._defaults.timeSuffix);
644 | this.timeDefined = true;
645 | if (hasChanged) this._updateDateTime();
646 | },
647 |
648 | //########################################################################
649 | // call custom onSelect.
650 | // bind to sliders slidestop, and grid click.
651 | //########################################################################
652 | _onSelectHandler: function() {
653 | var onSelect = this._defaults['onSelect'];
654 | var inputEl = this.$input ? this.$input[0] : null;
655 | if (onSelect && inputEl) {
656 | onSelect.apply(inputEl, [this.formattedDateTime, this]);
657 | }
658 | },
659 |
660 | //########################################################################
661 | // format the time all pretty...
662 | //########################################################################
663 | _formatTime: function(time, format, ampm) {
664 | if (ampm == undefined) ampm = this._defaults.ampm;
665 | time = time || { hour: this.hour, minute: this.minute, second: this.second, ampm: this.ampm, timezone: this.timezone };
666 | var tmptime = format || this._defaults.timeFormat.toString();
667 |
668 | if (ampm) {
669 | var hour12 = ((time.ampm == 'AM') ? (time.hour) : (time.hour % 12));
670 | hour12 = (Number(hour12) === 0) ? 12 : hour12;
671 | tmptime = tmptime.toString()
672 | .replace(/hh/g, ((hour12 < 10) ? '0' : '') + hour12)
673 | .replace(/h/g, hour12)
674 | .replace(/mm/g, ((time.minute < 10) ? '0' : '') + time.minute)
675 | .replace(/m/g, time.minute)
676 | .replace(/ss/g, ((time.second < 10) ? '0' : '') + time.second)
677 | .replace(/s/g, time.second)
678 | .replace(/TT/g, time.ampm.toUpperCase())
679 | .replace(/Tt/g, time.ampm.toUpperCase())
680 | .replace(/tT/g, time.ampm.toLowerCase())
681 | .replace(/tt/g, time.ampm.toLowerCase())
682 | .replace(/T/g, time.ampm.charAt(0).toUpperCase())
683 | .replace(/t/g, time.ampm.charAt(0).toLowerCase())
684 | .replace(/z/g, time.timezone);
685 | } else {
686 | tmptime = tmptime.toString()
687 | .replace(/hh/g, ((time.hour < 10) ? '0' : '') + time.hour)
688 | .replace(/h/g, time.hour)
689 | .replace(/mm/g, ((time.minute < 10) ? '0' : '') + time.minute)
690 | .replace(/m/g, time.minute)
691 | .replace(/ss/g, ((time.second < 10) ? '0' : '') + time.second)
692 | .replace(/s/g, time.second)
693 | .replace(/z/g, time.timezone);
694 | tmptime = $.trim(tmptime.replace(/t/gi, ''));
695 | }
696 |
697 | if (arguments.length) return tmptime;
698 | else this.formattedTime = tmptime;
699 | },
700 |
701 | //########################################################################
702 | // update our input with the new date time..
703 | //########################################################################
704 | _updateDateTime: function(dp_inst) {
705 | dp_inst = this.inst || dp_inst,
706 | dt = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay),
707 | dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
708 | formatCfg = $.datepicker._getFormatConfig(dp_inst),
709 | timeAvailable = dt !== null && this.timeDefined;
710 | this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
711 | var formattedDateTime = this.formattedDate;
712 | if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0))
713 | return;
714 |
715 | if (this._defaults.timeOnly === true) {
716 | formattedDateTime = this.formattedTime;
717 | } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
718 | formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
719 | }
720 |
721 | this.formattedDateTime = formattedDateTime;
722 |
723 | if(!this._defaults.showTimepicker) {
724 | this.$input.val(this.formattedDate);
725 | } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) {
726 | this.$altInput.val(this.formattedTime);
727 | this.$input.val(this.formattedDate);
728 | } else if(this.$altInput) {
729 | this.$altInput.val(formattedDateTime);
730 | this.$input.val(formattedDateTime);
731 | } else {
732 | this.$input.val(formattedDateTime);
733 | }
734 |
735 | this.$input.trigger("change");
736 | }
737 |
738 | });
739 |
740 | $.fn.extend({
741 | //########################################################################
742 | // shorthand just to use timepicker..
743 | //########################################################################
744 | timepicker: function(o) {
745 | o = o || {};
746 | var tmp_args = arguments;
747 |
748 | if (typeof o == 'object') tmp_args[0] = $.extend(o, { timeOnly: true });
749 |
750 | return $(this).each(function() {
751 | $.fn.datetimepicker.apply($(this), tmp_args);
752 | });
753 | },
754 |
755 | //########################################################################
756 | // extend timepicker to datepicker
757 | //########################################################################
758 | datetimepicker: function(o) {
759 | o = o || {};
760 | var $input = this,
761 | tmp_args = arguments;
762 |
763 | if (typeof(o) == 'string'){
764 | if(o == 'getDate')
765 | return $.fn.datepicker.apply($(this[0]), tmp_args);
766 | else
767 | return this.each(function() {
768 | var $t = $(this);
769 | $t.datepicker.apply($t, tmp_args);
770 | });
771 | }
772 | else
773 | return this.each(function() {
774 | var $t = $(this);
775 | $t.datepicker($.timepicker._newInst($t, o)._defaults);
776 | });
777 | }
778 | });
779 |
780 | //########################################################################
781 | // the bad hack :/ override datepicker so it doesnt close on select
782 | // inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
783 | //########################################################################
784 | $.datepicker._base_selectDate = $.datepicker._selectDate;
785 | $.datepicker._selectDate = function (id, dateStr) {
786 | var inst = this._getInst($(id)[0]),
787 | tp_inst = this._get(inst, 'timepicker');
788 |
789 | if (tp_inst) {
790 | tp_inst._limitMinMaxDateTime(inst, true);
791 | inst.inline = inst.stay_open = true;
792 | //This way the onSelect handler called from calendarpicker get the full dateTime
793 | this._base_selectDate(id, dateStr + tp_inst._defaults.separator + tp_inst.formattedTime + tp_inst._defaults.timeSuffix);
794 | inst.inline = inst.stay_open = false;
795 | this._notifyChange(inst);
796 | this._updateDatepicker(inst);
797 | }
798 | else this._base_selectDate(id, dateStr);
799 | };
800 |
801 | //#############################################################################################
802 | // second bad hack :/ override datepicker so it triggers an event when changing the input field
803 | // and does not redraw the datepicker on every selectDate event
804 | //#############################################################################################
805 | $.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
806 | $.datepicker._updateDatepicker = function(inst) {
807 |
808 | // don't popup the datepicker if there is another instance already opened
809 | var input = inst.input[0];
810 | if($.datepicker._curInst &&
811 | $.datepicker._curInst != inst &&
812 | $.datepicker._datepickerShowing &&
813 | $.datepicker._lastInput != input) {
814 | return;
815 | }
816 |
817 | if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {
818 |
819 | this._base_updateDatepicker(inst);
820 |
821 | // Reload the time control when changing something in the input text field.
822 | var tp_inst = this._get(inst, 'timepicker');
823 | if(tp_inst) tp_inst._addTimePicker(inst);
824 | }
825 | };
826 |
827 | //#######################################################################################
828 | // third bad hack :/ override datepicker so it allows spaces and colon in the input field
829 | //#######################################################################################
830 | $.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
831 | $.datepicker._doKeyPress = function(event) {
832 | var inst = $.datepicker._getInst(event.target),
833 | tp_inst = $.datepicker._get(inst, 'timepicker');
834 |
835 | if (tp_inst) {
836 | if ($.datepicker._get(inst, 'constrainInput')) {
837 | var ampm = tp_inst._defaults.ampm,
838 | dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
839 | datetimeChars = tp_inst._defaults.timeFormat.toString()
840 | .replace(/[hms]/g, '')
841 | .replace(/TT/g, ampm ? 'APM' : '')
842 | .replace(/Tt/g, ampm ? 'AaPpMm' : '')
843 | .replace(/tT/g, ampm ? 'AaPpMm' : '')
844 | .replace(/T/g, ampm ? 'AP' : '')
845 | .replace(/tt/g, ampm ? 'apm' : '')
846 | .replace(/t/g, ampm ? 'ap' : '') +
847 | " " +
848 | tp_inst._defaults.separator +
849 | tp_inst._defaults.timeSuffix +
850 | (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') +
851 | dateChars,
852 | chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
853 | return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
854 | }
855 | }
856 |
857 | return $.datepicker._base_doKeyPress(event);
858 | };
859 |
860 | //#######################################################################################
861 | // Override key up event to sync manual input changes.
862 | //#######################################################################################
863 | $.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
864 | $.datepicker._doKeyUp = function (event) {
865 | var inst = $.datepicker._getInst(event.target),
866 | tp_inst = $.datepicker._get(inst, 'timepicker');
867 |
868 | if (tp_inst) {
869 | if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) {
870 | try {
871 | $.datepicker._updateDatepicker(inst);
872 | }
873 | catch (err) {
874 | $.datepicker.log(err);
875 | }
876 | }
877 | }
878 |
879 | return $.datepicker._base_doKeyUp(event);
880 | };
881 |
882 | //#######################################################################################
883 | // override "Today" button to also grab the time.
884 | //#######################################################################################
885 | $.datepicker._base_gotoToday = $.datepicker._gotoToday;
886 | $.datepicker._gotoToday = function(id) {
887 | this._base_gotoToday(id);
888 | this._setTime(this._getInst($(id)[0]), new Date());
889 | };
890 |
891 | //#######################################################################################
892 | // Disable & enable the Time in the datetimepicker
893 | //#######################################################################################
894 | $.datepicker._disableTimepickerDatepicker = function(target, date, withDate) {
895 | var inst = this._getInst(target),
896 | tp_inst = this._get(inst, 'timepicker');
897 | $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
898 | if (tp_inst) {
899 | tp_inst._defaults.showTimepicker = false;
900 | tp_inst._updateDateTime(inst);
901 | }
902 | };
903 |
904 | $.datepicker._enableTimepickerDatepicker = function(target, date, withDate) {
905 | var inst = this._getInst(target),
906 | tp_inst = this._get(inst, 'timepicker');
907 | $(target).datepicker('getDate'); // Init selected[Year|Month|Day]
908 | if (tp_inst) {
909 | tp_inst._defaults.showTimepicker = true;
910 | tp_inst._addTimePicker(inst); // Could be disabled on page load
911 | tp_inst._updateDateTime(inst);
912 | }
913 | };
914 |
915 | //#######################################################################################
916 | // Create our own set time function
917 | //#######################################################################################
918 | $.datepicker._setTime = function(inst, date) {
919 | var tp_inst = this._get(inst, 'timepicker');
920 | if (tp_inst) {
921 | var defaults = tp_inst._defaults,
922 | // calling _setTime with no date sets time to defaults
923 | hour = date ? date.getHours() : defaults.hour,
924 | minute = date ? date.getMinutes() : defaults.minute,
925 | second = date ? date.getSeconds() : defaults.second;
926 |
927 | //check if within min/max times..
928 | if ((hour < defaults.hourMin || hour > defaults.hourMax) || (minute < defaults.minuteMin || minute > defaults.minuteMax) || (second < defaults.secondMin || second > defaults.secondMax)) {
929 | hour = defaults.hourMin;
930 | minute = defaults.minuteMin;
931 | second = defaults.secondMin;
932 | }
933 |
934 | tp_inst.hour = hour;
935 | tp_inst.minute = minute;
936 | tp_inst.second = second;
937 |
938 | if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour);
939 | if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute);
940 | if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second);
941 |
942 | tp_inst._onTimeChange();
943 | tp_inst._updateDateTime(inst);
944 | }
945 | };
946 |
947 | //#######################################################################################
948 | // Create new public method to set only time, callable as $().datepicker('setTime', date)
949 | //#######################################################################################
950 | $.datepicker._setTimeDatepicker = function(target, date, withDate) {
951 | var inst = this._getInst(target),
952 | tp_inst = this._get(inst, 'timepicker');
953 |
954 | if (tp_inst) {
955 | this._setDateFromField(inst);
956 | var tp_date;
957 | if (date) {
958 | if (typeof date == "string") {
959 | tp_inst._parseTime(date, withDate);
960 | tp_date = new Date();
961 | tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second);
962 | }
963 | else tp_date = new Date(date.getTime());
964 | if (tp_date.toString() == 'Invalid Date') tp_date = undefined;
965 | this._setTime(inst, tp_date);
966 | }
967 | }
968 |
969 | };
970 |
971 | //#######################################################################################
972 | // override setDate() to allow setting time too within Date object
973 | //#######################################################################################
974 | $.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
975 | $.datepicker._setDateDatepicker = function(target, date) {
976 | var inst = this._getInst(target),
977 | tp_date = (date instanceof Date) ? new Date(date.getTime()) : date;
978 |
979 | this._updateDatepicker(inst);
980 | this._base_setDateDatepicker.apply(this, arguments);
981 | this._setTimeDatepicker(target, tp_date, true);
982 | };
983 |
984 | //#######################################################################################
985 | // override getDate() to allow getting time too within Date object
986 | //#######################################################################################
987 | $.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
988 | $.datepicker._getDateDatepicker = function(target, noDefault) {
989 | var inst = this._getInst(target),
990 | tp_inst = this._get(inst, 'timepicker');
991 |
992 | if (tp_inst) {
993 | this._setDateFromField(inst, noDefault);
994 | var date = this._getDate(inst);
995 | if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second);
996 | return date;
997 | }
998 | return this._base_getDateDatepicker(target, noDefault);
999 | };
1000 |
1001 | //#######################################################################################
1002 | // override parseDate() because UI 1.8.14 throws an error about "Extra characters"
1003 | // An option in datapicker to ignore extra format characters would be nicer.
1004 | //#######################################################################################
1005 | $.datepicker._base_parseDate = $.datepicker.parseDate;
1006 | $.datepicker.parseDate = function(format, value, settings) {
1007 | var date;
1008 | try {
1009 | date = this._base_parseDate(format, value, settings);
1010 | } catch (err) {
1011 | // Hack! The error message ends with a colon, a space, and
1012 | // the "extra" characters. We rely on that instead of
1013 | // attempting to perfectly reproduce the parsing algorithm.
1014 | date = this._base_parseDate(format, value.substring(0,value.length-(err.length-err.indexOf(':')-2)), settings);
1015 | }
1016 | return date;
1017 | };
1018 |
1019 | //#######################################################################################
1020 | // override options setter to add time to maxDate(Time) and minDate(Time)
1021 | //#######################################################################################
1022 | $.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
1023 | $.datepicker._optionDatepicker = function(target, name, value) {
1024 | this._base_optionDatepicker(target, name, value);
1025 | var inst = this._getInst(target),
1026 | tp_inst = this._get(inst, 'timepicker');
1027 | if (tp_inst) {
1028 | //Set minimum and maximum date values if we have timepicker
1029 | if(name==='minDate') {
1030 | if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date)
1031 | tp_inst._defaults.minDateTime = new Date(value);
1032 | if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date)
1033 | tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
1034 | tp_inst._limitMinMaxDateTime(inst,true);
1035 | }
1036 | if(name==='maxDate') {
1037 | if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date)
1038 | tp_inst._defaults.maxDateTime = new Date(value);
1039 | if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date)
1040 | tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
1041 | tp_inst._limitMinMaxDateTime(inst,true);
1042 | }
1043 | }
1044 | };
1045 |
1046 | //#######################################################################################
1047 | // jQuery extend now ignores nulls!
1048 | //#######################################################################################
1049 | function extendRemove(target, props) {
1050 | $.extend(target, props);
1051 | for (var name in props)
1052 | if (props[name] === null || props[name] === undefined)
1053 | target[name] = props[name];
1054 | return target;
1055 | }
1056 |
1057 | $.timepicker = new Timepicker(); // singleton instance
1058 | $.timepicker.version = "0.9.6";
1059 |
1060 | })(jQuery);
1061 |
--------------------------------------------------------------------------------