├── src
├── models
│ ├── __init__.py
│ ├── models.py
│ ├── loadcsv.py
│ ├── quickstart.py
│ └── calendarapi.py
├── templates
│ ├── login.html
│ ├── finishoauth.html
│ ├── buttons.html
│ ├── dbdisplay.html
│ ├── bars.html
│ ├── calendar.html
│ ├── base.html
│ └── index.html
├── db
│ ├── events.sqlite3
│ ├── events_all.sqlite3
│ ├── events_all_2014.sqlite3
│ └── schema.sql
├── static
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.ttf
│ │ ├── glyphicons-halflings-regular.woff
│ │ └── glyphicons-halflings-regular.svg
│ ├── css
│ │ ├── calendar.css
│ │ ├── bars.css
│ │ ├── cover.css
│ │ ├── d3.tip.v0.6.3.js
│ │ ├── bootstrap-theme.min.css
│ │ ├── bootstrap-theme.css.map
│ │ └── bootstrap-theme.css
│ ├── calendar.js
│ ├── bars.js
│ └── js
│ │ └── bootstrap.min.js
├── client_secrets.json
└── webapp.py
├── Procfile
├── .gitignore
├── package.json
├── gruntfile.js
├── ANALYSIS.md
├── requirements.txt
├── MOTIVATION.md
└── README.md
/src/models/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: python webapp.py
2 |
--------------------------------------------------------------------------------
/src/templates/login.html:
--------------------------------------------------------------------------------
1 | {% for calendar in calendar_list %}
2 | {{ calendar }}
3 | {% endfor %}
--------------------------------------------------------------------------------
/src/db/events.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert8138/flask-google-calendar-api-project/HEAD/src/db/events.sqlite3
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | client_secret.json
2 | pip-selfcheck.json
3 | events.csv
4 | *.pyc
5 | bin/
6 | include/
7 | lib/
8 | node_modules/
--------------------------------------------------------------------------------
/src/db/events_all.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert8138/flask-google-calendar-api-project/HEAD/src/db/events_all.sqlite3
--------------------------------------------------------------------------------
/src/db/events_all_2014.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert8138/flask-google-calendar-api-project/HEAD/src/db/events_all_2014.sqlite3
--------------------------------------------------------------------------------
/src/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert8138/flask-google-calendar-api-project/HEAD/src/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/src/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert8138/flask-google-calendar-api-project/HEAD/src/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "livereloadTest",
3 | "version": "0.1.0",
4 | "devDependencies": {
5 | "grunt": "~0.4.2",
6 | "grunt-contrib-watch": "~0.5.3"
7 | }
8 | }
--------------------------------------------------------------------------------
/src/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert8138/flask-google-calendar-api-project/HEAD/src/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/src/db/schema.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS events;
2 | CREATE TABLE events (
3 | id INTEGER PRIMARY KEY AUTOINCREMENT,
4 | date TEXT,
5 | duration REAL,
6 | event_type TEXT,
7 | event_name TEXT
8 | );
9 |
--------------------------------------------------------------------------------
/src/templates/finishoauth.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %} OAuth authentication success {% endblock %}
4 |
5 | {% block content %}
6 |
7 | OAuth authentication was successful!
8 |
9 | {% endblock %}
--------------------------------------------------------------------------------
/src/templates/buttons.html:
--------------------------------------------------------------------------------
1 | {% for calendar in calendarMap %}
2 |
3 |
6 |
7 | {% endfor %}
--------------------------------------------------------------------------------
/src/static/css/calendar.css:
--------------------------------------------------------------------------------
1 | .day {
2 | fill: #fff;
3 | stroke: #ccc;
4 | }
5 |
6 | .month {
7 | fill: none;
8 | stroke: #000;
9 | stroke-width: 2px;
10 | }
11 |
12 | .heatmap {
13 | fill: #2b8cbe;
14 | }
15 |
16 | .calendar {
17 | display: block;
18 | margin: auto;
19 | }
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | grunt.initConfig({
3 | pkg: grunt.file.readJSON('package.json'),
4 | watch: {
5 | src: {
6 | files: ['src/templates/*.html', 'src/static/css/*.css'],
7 | options: { livereload: true }
8 | }
9 | }
10 | });
11 | grunt.loadNpmTasks('grunt-contrib-watch');
12 | };
--------------------------------------------------------------------------------
/ANALYSIS.md:
--------------------------------------------------------------------------------
1 | # A list of Analysis Questions About My Calendar:
2 |
3 | * How much time do I spend on Continuous Improvement (CI)? On the weekdays? On the weekends?
4 | * How are the patterns different from day time and night time, especially on the weekends?
5 | * How much time do I spend on Self Reflection, Reading, and Writing?
6 | * How much Vacation Days Do I take a Year?
7 | * How much time do I spend working while in Graduate School v.s. Working in the Industry?
8 |
--------------------------------------------------------------------------------
/src/client_secrets.json:
--------------------------------------------------------------------------------
1 | {
2 | "installed": {
3 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
4 | "auth_uri": "https://accounts.google.com/o/oauth2/auth",
5 | "client_id": "314221214870-o3arqdr7ohjjbf7cguijsoelpej0ol5k.apps.googleusercontent.com",
6 | "client_secret": "NzxDBF4dbANc3nDI-HQhcb0g",
7 | "redirect_uris": [
8 | "urn:ietf:wg:oauth:2.0:oob",
9 | "http://localhost"
10 | ],
11 | "token_uri": "https://accounts.google.com/o/oauth2/token"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/templates/dbdisplay.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %} Events {% endblock %}
4 |
5 | {% block content %}
6 |
7 |
8 | {% for event in events %}
9 |
10 |
11 | | {{ event.date }} |
12 | {{ event.duration }} |
13 | {{ event.event_type }} |
14 | {{ event.event_name }} |
15 |
16 | {% endfor %}
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/src/templates/bars.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %} Bar View {% endblock %}
4 |
5 | {% block content %}
6 |
7 | {% with calendarMap=calendarMap %}
8 | {% include "buttons.html" %}
9 | {% endwith %}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {% endblock %}
--------------------------------------------------------------------------------
/src/templates/calendar.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %} Calendar View {% endblock %}
4 |
5 | {% block content %}
6 |
7 | {% with calendarMap=calendarMap %}
8 | {% include "buttons.html" %}
9 | {% endwith %}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {% endblock %}
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==0.10.1
2 | Flask-Cors==2.1.0
3 | Flask-SQLAlchemy==2.0
4 | Jinja2==2.8
5 | MarkupSafe==0.23
6 | Pattern==2.6
7 | Pygments==1.6
8 | SQLAlchemy==1.0.8
9 | Sphinx==1.2.3
10 | Werkzeug==0.10.4
11 | backports.ssl-match-hostname==3.4.0.2
12 | cdk==1.0.8
13 | certifi==14.05.14
14 | distribute==0.6.45
15 | docopt==0.6.2
16 | google-api-python-client==1.4.1
17 | httplib2==0.9.1
18 | ipython==1.1.0
19 | itsdangerous==0.24
20 | oauth2client==1.5.0
21 | pyasn1==0.1.8
22 | pyasn1-modules==0.0.7
23 | python-dateutil==2.2
24 | pytz==2014.7
25 | pyzmq==14.4.0
26 | readline==6.2.4.1
27 | requests==2.7.0
28 | rsa==3.2
29 | simplejson==3.8.0
30 | six==1.9.0
31 | tornado==4.0.2
32 | uritemplate==0.6
33 | vboxapi==1.0
34 | vertica-python==0.3.0
35 | virtualenv==1.10.1
36 | wheel==0.24.0
37 | wsgiref==0.1.2
38 |
--------------------------------------------------------------------------------
/src/models/models.py:
--------------------------------------------------------------------------------
1 | from flask.ext.sqlalchemy import SQLAlchemy
2 | from sqlalchemy import Column, Integer, String, Date, Float
3 |
4 | db = SQLAlchemy()
5 |
6 | class Events(db.Model):
7 | __tablename__ = 'events_full'
8 | id = Column(Integer, primary_key=True)
9 | date = Column(Date)
10 | duration = Column(Float)
11 | event_type = Column(String)
12 | event_name = Column(String)
13 |
14 | def __init__(self, date, duration, event_type, event_name):
15 | self.date = date
16 | self.duration = duration
17 | self.event_type = event_type
18 | self.event_name = event_name
19 |
20 | def __repr__(self):
21 | return '' % (self.id)
22 |
23 | @property
24 | def serialize(self):
25 | '''return as a json object so we can use it in RESTful API'''
26 | return {'id': self.id,
27 | 'date': self.date.strftime("%Y-%m-%d"),
28 | 'duration': self.duration,
29 | 'event_type': self.event_type,
30 | 'event_name': self.event_name }
31 |
32 |
--------------------------------------------------------------------------------
/src/static/css/bars.css:
--------------------------------------------------------------------------------
1 | .bar {
2 | fill: orange;
3 | }
4 |
5 | .bar:hover {
6 | fill: #2ca25f;
7 | }
8 |
9 | .xaxis path,
10 | .xaxis line {
11 | fill: none;
12 | stroke: black;
13 | shape-rendering: crispEdges;
14 | }
15 | .yaxis path,
16 | .yaxis line {
17 | fill: none;
18 | stroke: black;
19 | shape-rendering: crispEdges;
20 | }
21 |
22 | .spanClass
23 | {
24 | display:block;
25 | }
26 |
27 | .d3-tip {
28 | line-height: 1;
29 | font-weight: bold;
30 | padding: 12px;
31 | background: rgba(0, 0, 0, 0.8);
32 | color: #fff;
33 | border-radius: 2px;
34 | }
35 |
36 | /* Creates a small triangle extender for the tooltip */
37 | .d3-tip:after {
38 | box-sizing: border-box;
39 | display: inline;
40 | font-size: 10px;
41 | width: 100%;
42 | line-height: 1;
43 | color: rgba(0, 0, 0, 0.8);
44 | content: "\25BC";
45 | position: absolute;
46 | text-align: center;
47 | }
48 |
49 | /* Style northward tooltips differently */
50 | .d3-tip.n:after {
51 | margin: -1px 0 0 0;
52 | top: 100%;
53 | left: 0;
54 | }
--------------------------------------------------------------------------------
/src/models/loadcsv.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, Date, Float
2 | from sqlalchemy import create_engine, MetaData, Table
3 | from sqlalchemy.orm import mapper, create_session
4 | import datetime
5 | import csv
6 |
7 | def load_csv_to_db(CSV_FILE):
8 |
9 | #CSV_FILE = 'events.csv'
10 | engine = create_engine('sqlite:///db/events_all_2014.sqlite3') # memory-only database
11 |
12 | table = None
13 | metadata = MetaData(bind=engine)
14 |
15 |
16 | with open(CSV_FILE) as f:
17 | # assume first line is header
18 | cf = csv.DictReader(f,
19 | fieldnames = ('date', 'duration', 'event_type', 'event_name'),
20 | delimiter=',')
21 | print "Ready to write into db..................................."
22 | for row in cf:
23 | if table is None:
24 | # create the table
25 | table = Table('events_full', metadata,
26 | Column('id', Integer, primary_key=True),
27 | Column('date', Date),
28 | Column('duration', Float),
29 | Column('event_type', String),
30 | Column('event_name', String)
31 | )
32 | table.create()
33 | # insert data into the table
34 | date = row['date']
35 | duration = row['duration']
36 | event_type = row['event_type']
37 | event_name = row['event_name'].replace(',', '')
38 | year, month, day = [int(elem) for elem in date.split('-')]
39 | date = datetime.date(year, month, day)
40 | table.insert().values(date = date,
41 | duration = duration,
42 | event_type = event_type,
43 | event_name = event_name).execute()
--------------------------------------------------------------------------------
/src/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {% block title %}{% endblock %}
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
40 |
41 | {% block content %}
42 | {% endblock %}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Welcome to Calendar Aanalyzer
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
40 |
41 |
42 |
Analyze your Google Calendar
43 |
This Flask App analyze how you spent your time by querying Google Calendar API and build beautiful d3 graphs
44 |
45 | Get Started
46 |
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/models/quickstart.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import httplib2
3 | import os
4 |
5 | from apiclient import discovery
6 | import oauth2client
7 | from oauth2client import client
8 | from oauth2client import tools
9 |
10 | import datetime
11 |
12 | try:
13 | import argparse
14 | flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
15 | except ImportError:
16 | flags = None
17 |
18 | SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'
19 | CLIENT_SECRET_FILE = 'client_secret.json'
20 | APPLICATION_NAME = 'Google Calendar API Quickstart'
21 |
22 |
23 | def get_credentials():
24 | """Gets valid user credentials from storage.
25 |
26 | If nothing has been stored, or if the stored credentials are invalid,
27 | the OAuth2 flow is completed to obtain the new credentials.
28 |
29 | Returns:
30 | Credentials, the obtained credential.
31 | """
32 | home_dir = os.path.expanduser('~')
33 | credential_dir = os.path.join(home_dir, '.credentials')
34 | if not os.path.exists(credential_dir):
35 | os.makedirs(credential_dir)
36 | credential_path = os.path.join(credential_dir,
37 | 'calendar-quickstart.json')
38 |
39 | store = oauth2client.file.Storage(credential_path)
40 | credentials = store.get()
41 | if not credentials or credentials.invalid:
42 | flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
43 | flow.user_agent = APPLICATION_NAME
44 | if flags:
45 | credentials = tools.run_flow(flow, store, flags)
46 | else: # Needed only for compatability with Python 2.6
47 | credentials = tools.run(flow, store)
48 | print('Storing credentials to ' + credential_path)
49 | return credentials
50 |
51 | def main():
52 | """Shows basic usage of the Google Calendar API.
53 |
54 | Creates a Google Calendar API service object and outputs a list of the next
55 | 10 events on the user's calendar.
56 | """
57 | credentials = get_credentials()
58 | http = credentials.authorize(httplib2.Http())
59 | service = discovery.build('calendar', 'v3', http=http)
60 |
61 | now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
62 | print('Getting the upcoming 10 events')
63 | eventsResult = service.events().list(
64 | calendarId='primary', timeMin=now, maxResults=10, singleEvents=True,
65 | orderBy='startTime').execute()
66 | events = eventsResult.get('items', [])
67 |
68 | if not events:
69 | print('No upcoming events found.')
70 | for event in events:
71 | start = event['start'].get('dateTime', event['start'].get('date'))
72 | print(start, event['summary'])
73 |
74 |
75 | if __name__ == '__main__':
76 | main()
--------------------------------------------------------------------------------
/src/static/calendar.js:
--------------------------------------------------------------------------------
1 | $.each(window.calendarMap, function(k, v) {
2 | d3.select("[id='" + k + "']").on("click", function() { make_calendar(k); });
3 | });
4 |
5 | var width = 1000,
6 | height = 150,
7 | cellSize = 15;
8 |
9 | var percent = d3.format(".1%"),
10 | format = d3.time.format("%Y-%m-%d");
11 |
12 | // Set up the skeleton by year
13 | var svg = d3.select("body").selectAll("svg")
14 | .data(d3.range(2013, 2016))
15 | .enter().append("svg")
16 | .attr("width", width)
17 | .attr("height", height)
18 | .attr("class", "calendar")
19 | .append("g")
20 | .attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (height - cellSize * 7 - 1) + ")");
21 |
22 | // Set up the YEAR text
23 | svg.append("text")
24 | .attr("transform", "translate(-6," + cellSize * 3.5 + ")rotate(-90)")
25 | .style("text-anchor", "middle")
26 | .text(function(d) { return d; });
27 |
28 | // Set up each day for plotting
29 | var rect = svg.selectAll(".day")
30 | .data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
31 | .enter().append("rect")
32 | .attr("class", "day")
33 | .attr("width", cellSize)
34 | .attr("height", cellSize)
35 | .attr("x", function(d) { return d3.time.weekOfYear(d) * cellSize; })
36 | .attr("y", function(d) { return d.getDay() * cellSize; })
37 | .datum(format);
38 |
39 | // Add title/date to each cell
40 | rect.append("title")
41 | .text(function(d) { return d; });
42 |
43 | // Set up each month for plotting
44 | svg.selectAll(".month")
45 | .data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
46 | .enter().append("path")
47 | .attr("class", "month")
48 | .attr("d", monthPath);
49 |
50 | // Some voodoo magic that I don't understand about paths
51 | function monthPath(t0) {
52 | var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
53 | d0 = t0.getDay(),
54 | w0 = d3.time.weekOfYear(t0),
55 | d1 = t1.getDay(),
56 | w1 = d3.time.weekOfYear(t1);
57 | return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
58 | + "H" + w0 * cellSize + "V" + 7 * cellSize
59 | + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
60 | + "H" + (w1 + 1) * cellSize + "V" + 0
61 | + "H" + (w0 + 1) * cellSize + "Z";
62 | }
63 |
64 | function make_calendar(event_type) {
65 |
66 | url = "http://127.0.0.1:5000/api/".concat(event_type)
67 | d3.json(url, function(error, data) {
68 |
69 | console.log(event_type.concat("is loaded!"))
70 |
71 | var dataset = data['json_list']
72 |
73 | var dataset_munged = d3.nest()
74 | .key(function(d) { return format(new Date(d.date)); })
75 | .rollup(function(events) { return d3.sum(events, function(d) {
76 | return d.duration|| 0; }); })
77 | .map(dataset);
78 |
79 | var colors = ["#ffffd9","#edf8b1","#c7e9b4",
80 | "#7fcdbb","#41b6c4","#1d91c0",
81 | "#225ea8","#253494","#081d58"]
82 | var colorScale = d3.scale.quantize()
83 | // .domain([0, d3.max(dataset_munged, function (d) { return d.value; })])
84 | .domain([0, 200]) //TODO
85 | .range(colors);
86 |
87 | rect
88 | .classed("day", true)
89 | .style("fill", function(d) { return colorScale(dataset_munged[d]); })
90 |
91 | })
92 | }
--------------------------------------------------------------------------------
/src/static/css/cover.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Globals
3 | */
4 |
5 | /* Links */
6 | a,
7 | a:focus,
8 | a:hover {
9 | color: #fff;
10 | }
11 |
12 | /* Custom default button */
13 | .btn-default,
14 | .btn-default:hover,
15 | .btn-default:focus {
16 | color: white;
17 | text-shadow: none; /* Prevent inheritence from `body` */
18 | background-color: #fff;
19 | border: 1px solid #fff;
20 | }
21 |
22 |
23 | /*
24 | * Base structure
25 | */
26 |
27 | html,
28 | body {
29 | height: 100%;
30 | background-color: #fdae6b;
31 | }
32 | body {
33 | color: #fff;
34 | text-align: center;
35 | text-shadow: 0 1px 3px rgba(0,0,0,.5);
36 | }
37 |
38 | /* Extra markup and styles for table-esque vertical and horizontal centering */
39 | .site-wrapper {
40 | display: table;
41 | width: 100%;
42 | height: 100%; /* For at least Firefox */
43 | min-height: 100%;
44 | -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5);
45 | box-shadow: inset 0 0 100px rgba(0,0,0,.5);
46 | }
47 | .site-wrapper-inner {
48 | display: table-cell;
49 | vertical-align: top;
50 | }
51 | .cover-container {
52 | margin-right: auto;
53 | margin-left: auto;
54 | }
55 |
56 | /* Padding for spacing */
57 | .inner {
58 | padding: 30px;
59 | }
60 |
61 |
62 | /*
63 | * Header
64 | */
65 | .masthead-brand {
66 | margin-top: 10px;
67 | margin-bottom: 10px;
68 | }
69 |
70 | .masthead-nav > li {
71 | display: inline-block;
72 | }
73 | .masthead-nav > li + li {
74 | margin-left: 20px;
75 | }
76 | .masthead-nav > li > a {
77 | padding-right: 0;
78 | padding-left: 0;
79 | font-size: 16px;
80 | font-weight: bold;
81 | color: #fff; /* IE8 proofing */
82 | color: rgba(255,255,255,.75);
83 | border-bottom: 2px solid transparent;
84 | }
85 | .masthead-nav > li > a:hover,
86 | .masthead-nav > li > a:focus {
87 | background-color: transparent;
88 | border-bottom-color: #a9a9a9;
89 | border-bottom-color: rgba(255,255,255,.25);
90 | }
91 | .masthead-nav > .active > a,
92 | .masthead-nav > .active > a:hover,
93 | .masthead-nav > .active > a:focus {
94 | color: #fff;
95 | border-bottom-color: #fff;
96 | }
97 |
98 | @media (min-width: 768px) {
99 | .masthead-brand {
100 | float: left;
101 | }
102 | .masthead-nav {
103 | float: right;
104 | }
105 | }
106 |
107 |
108 | /*
109 | * Cover
110 | */
111 |
112 | .cover {
113 | padding: 0 20px;
114 | }
115 | .cover .btn-lg {
116 | padding: 10px 20px;
117 | font-weight: bold;
118 | }
119 |
120 |
121 | /*
122 | * Footer
123 | */
124 |
125 | .mastfoot {
126 | color: #999; /* IE8 proofing */
127 | color: rgba(255,255,255,.5);
128 | }
129 |
130 |
131 | /*
132 | * Affix and center
133 | */
134 |
135 | @media (min-width: 768px) {
136 | /* Pull out the header and footer */
137 | .masthead {
138 | position: fixed;
139 | top: 0;
140 | }
141 | .mastfoot {
142 | position: fixed;
143 | bottom: 0;
144 | }
145 | /* Start the vertical centering */
146 | .site-wrapper-inner {
147 | vertical-align: middle;
148 | }
149 | /* Handle the widths */
150 | .masthead,
151 | .mastfoot,
152 | .cover-container {
153 | width: 100%; /* Must be percentage or pixels for horizontal alignment */
154 | }
155 | }
156 |
157 | @media (min-width: 992px) {
158 | .masthead,
159 | .mastfoot,
160 | .cover-container {
161 | width: 700px;
162 | }
163 | }
--------------------------------------------------------------------------------
/src/static/bars.js:
--------------------------------------------------------------------------------
1 | $.each(window.calendarMap, function(k, v) {
2 | d3.select("[id='" + k + "']").on("click", function() { make_graph(k); });
3 | });
4 |
5 | var width = 1300,
6 | height = 250;
7 |
8 | var svg = d3.select("body").append("svg")
9 | .attr("width", width)
10 | .attr("height", height);
11 |
12 | var tip = d3.tip()
13 | .attr('class', 'd3-tip')
14 | .offset([-10, 0])
15 | .html(function(d) {
16 | return "event: " + d.event_name + "" +
17 | "date: " + d.date + "";
18 | })
19 |
20 | //http://bl.ocks.org/Caged/6476579
21 | svg.call(tip);
22 |
23 | function make_graph(event_type) {
24 | url = "http://127.0.0.1:5000/api/".concat(event_type)
25 | console.log(url)
26 | d3.json(url, function(error, data) {
27 |
28 | console.log(event_type.concat(" is loaded!"))
29 | console.log(data['json_list'])
30 |
31 | var dataset = data['json_list']
32 | var barwidth = width / dataset.length;
33 |
34 | // http://bost.ocks.org/mike/bar/3/
35 | var x = d3.scale.linear()
36 | .domain([0, dataset.length])
37 | .range([0, width]);
38 |
39 | var y = d3.scale.linear()
40 | .domain([0, d3.max(dataset, function(d) { return d.duration; })])
41 | .range([height - 30, 0]);
42 |
43 | var xAxis = d3.svg.axis()
44 | .scale(x)
45 | .ticks(25)
46 | .orient("bottom");
47 |
48 | var yAxis = d3.svg.axis()
49 | .scale(y)
50 | .ticks(5)
51 | .orient("left");
52 |
53 | // This is more of a hack, because there is no transition
54 | // I basically removed the old axes, rebuild news ones, and plot them
55 |
56 | svg.selectAll("g").remove();
57 |
58 | svg.append("g")
59 | .attr("class", "xaxis")
60 | .attr("transform", "translate(50," + (height - 30 - 10) + ")")
61 | .transition()
62 | .duration(750)
63 | .call(xAxis);
64 |
65 | svg.append("g")
66 | .attr("class", "yaxis")
67 | .attr("transform", "translate(50, 0)")
68 | .transition()
69 | .duration(750)
70 | .call(yAxis);
71 |
72 |
73 | // http://bl.ocks.org/mbostock/3808218
74 | // Enter, Update, Exit Design Pattern - suggested by Krist
75 |
76 | var bars = svg.selectAll(".bar")
77 | .data(dataset);
78 |
79 | bars.enter()
80 | .append("rect")
81 | .classed("bar", true)
82 | .transition()
83 | .duration(2000)
84 | .attr("transform", function(d, i) {
85 | return "translate(" + (i * barwidth + 50) + ",0)"; })
86 | .attr("y", function(d) { return y(d.duration); })
87 | .attr("height", function(d) { return height - 40 - y(d.duration); })
88 | .attr("width", barwidth - 1)
89 | .attr("fill", "#fdae6b");
90 |
91 | bars
92 | .attr("transform", function(d, i) {
93 | return "translate(" + (i * barwidth + 50) + ",0)"; })
94 | .attr("y", function(d) { return y(d.duration); })
95 | .attr("height", function(d) { return height - 40 - y(d.duration); })
96 | .attr("width", barwidth - 1);
97 |
98 | bars.exit().remove();
99 |
100 | bars.on('mouseover', tip.show)
101 | .on('mouseout', tip.hide);
102 |
103 | });
104 | }
--------------------------------------------------------------------------------
/MOTIVATION.md:
--------------------------------------------------------------------------------
1 | # Building a Interactive Data Visualization Application using Python Flask and Google Calendar API
2 |
3 | ## Motivation
4 | Right around 02/07/2015, I asked the following [question] on Quora:
5 |
6 | > As a data scientist, what are the things that I can learn from full stack developers so that I can build interesting web applications for data science?
7 | I came across Hadley Wickham's slide from Conference | Simply Statistics, where he rightly pointed out that a lot of the statistical innovations are moving to web:
8 |
9 | > PDF -> HTML
10 | LaTex -> Markdown
11 | Static -> Interactive
12 |
13 | > More people are sharing their codes and analyses in the form of Rmarkdown or iPython notebooks. Many are pushing their new R/Python packages to Github, and we see increasingly better integration of data and web applications (e.g. ŷhat | Data Science Operations Platform, Domino Data Lab ... etc)
14 |
15 | > What are the things that data scientists can learn about building web application that makes our work more effective, engaging, and highly visible?
16 |
17 | Very soon, I came across Alexander Blocker's awesome answer, which has served as the backbone for my exploration of this area. In his words:
18 |
19 | > If you want to build a web application based on an analysis, here are a few skills I would start with:
20 |
21 | > * Learn how to map your data and analysis to a **RESTful API** (Representational state transfer). Data scientists usually like to work with tabular data, often linked across tables by common identifiers and accessed via SQL. Translating this to a set of nouns and verbs in the REST style is a great step towards making your tools and results more widely usable.
22 |
23 | > * Learn basic **AJAX** so you can **hit your RESTful API from client-side code**. I find **jQuery** the nicest way to get started with this. Also, don't get hung up on low-level DOM manipulation in Javascript. Learn Selectors and take advantage of all the hard work jQuery developers have put into the problem.
24 |
25 | > * Learn how to approach layout on the web. This means **HTML + CSS. Twitter's Bootstrap framework** is a lovely, well-designed starting point.
26 | Learn how to build interactive graphics on the client side. D3.js is the most mature way to do this, but it's verbose. Plottable (Home, from Palantir) and NVd3 (nvd3-community/nvd3, recently revived) are nice layers of abstraction over the details of D3.
27 |
28 | > * To experiment with all of this, a platform like Google App Engine is a nice place to start. It handles a lot of the operational details and lets you focus on implementing the application. I would suggest starting in **Python**, if that's what you're comfortable in, or Go, which I prefer. Go on App Engine is still in its early days, but it's easy to write web services in, runs and compiles fast, and the App Engine-specific parts are lightweight. The core of the code is just the Go standard library, which makes it easier to move to Google Compute Engine, **AWS**, or anywhere else you want.
29 |
30 | > Finally, a bit on **mindset**. I like to think of building web applications as **another way to express your ideas**, just like giving talks, writing papers, or distributing R packages. A working prototype based on a new algorithm or dataset is often far more compelling than a report or command-line tool, especially to non-data-scientists. Your web apps don't have to be completely polished and productionised to be useful in this capacity. But, you do need to communicate your work just as clearly in an interactive app as would would in any other medium.
31 |
32 | [question]: https://www.quora.com/As-a-data-scientist-what-are-the-things-that-I-can-learn-from-full-stack-developers-so-that-I-can-build-interesting-web-applications-for-data-science
--------------------------------------------------------------------------------
/src/models/calendarapi.py:
--------------------------------------------------------------------------------
1 | import os
2 | import httplib2
3 | import oauth2client
4 | import datetime
5 | import time
6 | import csv
7 | from apiclient import discovery
8 | from oauth2client import client
9 | from oauth2client import tools
10 | from collections import defaultdict
11 |
12 | # Global Constants
13 | SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'
14 | CLIENT_SECRET_FILE = 'client_secret.json'
15 | APPLICATION_NAME = 'Google Calendar API Quickstart'
16 |
17 | # Helper Functions
18 | def get_credentials():
19 | """Gets valid user credentials from storage.
20 |
21 | If nothing has been stored, or if the stored credentials are invalid,
22 | the OAuth2 flow is completed to obtain the new credentials.
23 |
24 | Returns:
25 | Credentials, the obtained credential.
26 | """
27 | try:
28 | import argparse
29 | flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
30 | except ImportError:
31 | flags = None
32 |
33 | home_dir = os.path.expanduser('~')
34 | credential_dir = os.path.join(home_dir, '.credentials')
35 | if not os.path.exists(credential_dir):
36 | os.makedirs(credential_dir)
37 | credential_path = os.path.join(credential_dir,
38 | 'calendar-quickstart.json')
39 |
40 | store = oauth2client.file.Storage(credential_path)
41 | credentials = store.get()
42 | if not credentials or credentials.invalid:
43 | flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
44 | flow.user_agent = APPLICATION_NAME
45 | if flags:
46 | credentials = tools.run_flow(flow, store, flags)
47 | else: # Needed only for compatability with Python 2.6
48 | credentials = tools.run(flow, store)
49 | print 'Storing credentials to ' + credential_path
50 | return credentials
51 |
52 | def build_service():
53 | """Build a Google Calendar API query endpoint"""
54 | credentials = get_credentials()
55 | http = credentials.authorize(httplib2.Http())
56 | service = discovery.build('calendar', 'v3', http=http)
57 | return service
58 |
59 | def get_start_date(startDate):
60 | """
61 | Given a string startDate in the format of YYYY-mm-dd
62 | return a datetime object with the proper time zone
63 | """
64 | return (datetime.datetime
65 | .strptime(startDate, "%Y-%m-%d")
66 | .isoformat() + 'Z')
67 |
68 | def get_now_date():
69 | """Return a datetime object of current time"""
70 | return (datetime.datetime.utcnow()
71 | .isoformat() + 'Z')
72 |
73 | def get_end_date():
74 | pass
75 |
76 | def get_ts_from_datetime(start, end):
77 | """convert datetime string into a time event
78 | so we can perform time arithmetic on them"""
79 |
80 | if start[-2:] == '00':
81 | startHr = datetime.datetime.strptime(start[:-6], "%Y-%m-%dT%H:%M:%S")
82 | elif start[-1] == 'Z':
83 | startHr = datetime.datetime.strptime(start, "%Y-%m-%dT%H:%M:%SZ")
84 | else:
85 | startHr = datetime.datetime.strptime(start, "%Y-%m-%d")
86 |
87 | if end[-2:] == '00':
88 | endHr = datetime.datetime.strptime(end[:-6], "%Y-%m-%dT%H:%M:%S")
89 | elif end[-1] == 'Z':
90 | endHr = datetime.datetime.strptime(end, "%Y-%m-%dT%H:%M:%SZ")
91 | else:
92 | endHr = datetime.datetime.strptime(end, "%Y-%m-%d")
93 |
94 | startTS = time.mktime(startHr.timetuple())
95 | endTS = time.mktime(endHr.timetuple())
96 | return (startTS, endTS)
97 |
98 | def get_calendar_list_map(service):
99 | """Get the list of Calendar Names, and store their corresponding ids"""
100 |
101 | calendarMap = defaultdict()
102 | page_token = None
103 |
104 | calendars = (service.calendarList()
105 | .list(pageToken = page_token).execute())
106 |
107 | for calendar in calendars['items']:
108 | id = calendar.get('id', None)
109 | summary = calendar.get('summary', None)
110 | calendarMap[summary] = id
111 | return calendarMap
112 |
113 | def get_events_in_calendar(calendarName, calendarMap, service, startDate, endDate=None):
114 | """Get all the events in a calendar for a specific time period"""
115 |
116 | print '\nGet ' + calendarName + ' Events Since the beginning of the year.............'
117 | startDate = get_start_date(startDate)
118 | endDate = get_now_date()
119 | # service = build_service()
120 | # calendarMap = get_calendar_list_map(service)
121 | calendarId = calendarMap.get(calendarName)
122 | eventsResult = (service.events().list(calendarId = calendarId, timeMin = startDate, timeMax = endDate, maxResults = 1000, singleEvents = True, orderBy = 'startTime').execute())
123 |
124 | events = eventsResult.get('items', [])
125 | events_list = []
126 |
127 | if not events:
128 | print 'No events found.'
129 | for event in events:
130 | start = event['start'].get('dateTime', event['start'].get('date'))
131 | end = event['end'].get('dateTime', event['end'].get('date'))
132 | startTS, endTS = get_ts_from_datetime(start, end)
133 |
134 | duration = (endTS - startTS) / 60
135 | event_type = calendarName
136 |
137 | eventTitle = event['summary']
138 | event_name = ''.join([i if ord(i) < 128 else '' for i in eventTitle.replace(',','')])
139 |
140 | #print start[:10], duration, event_type, event_name
141 |
142 | events_list.append([start[:10], duration, event_type, event_name])
143 | return events_list
144 |
145 | def write_events_to_csv(events_list):
146 | with open('events.csv', 'a') as events_csv:
147 | writer = csv.writer(events_csv)
148 | for each_event in events_list:
149 | writer.writerow(each_event)
150 |
151 | def main():
152 | """Main Entry Point of the App"""
153 |
154 | service = build_service()
155 | calendarMap = get_calendar_list_map(service)
156 |
157 | for calendarName in calendarMap:
158 | events_list = get_events_in_calendar(calendarName, calendarMap, '2013-01-01')
159 | write_events_to_csv(events_list)
160 |
161 | if __name__ == '__main__':
162 | main()
163 |
--------------------------------------------------------------------------------
/src/webapp.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask import request
3 | from flask import render_template
4 | from flask import jsonify
5 | from apiclient import discovery
6 | from oauth2client import client
7 | from flask.ext.cors import CORS
8 | from oauth2client import client
9 | from sqlalchemy import *
10 | from models.models import *
11 | from models.calendarapi import *
12 | from models.loadcsv import *
13 | import sqlite3
14 | import datetime
15 | import os
16 | import flask
17 | import httplib2
18 |
19 | webapp = Flask(__name__)
20 | webapp.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db/events_all_2014.sqlite3'
21 | webapp.config['SESSION_TYPE'] = 'filesystem'
22 | webapp.config['SECRET_KEY'] = 'super secret key'
23 | CORS(webapp)
24 | db.init_app(webapp)
25 |
26 | # event_type_map = {'studytime': 'Study Time',
27 | # 'outsidereading': 'Outside Reading',
28 | # 'birthdays': 'Birthdays',
29 | # 'misc': 'Misc',
30 | # 'deadline': 'Deadline',
31 | # 'exercise': 'Exercise'}
32 |
33 | @webapp.route('/', methods=['GET','POST'])
34 | def index():
35 | return render_template('index.html')
36 |
37 | @webapp.route('/login')
38 | def login():
39 | if 'credentials' not in flask.session:
40 | return flask.redirect(flask.url_for('oauth2callback'))
41 | credentials = client.OAuth2Credentials.from_json(flask.session['credentials'])
42 | if credentials.access_token_expired:
43 | return flask.redirect(flask.url_for('oauth2callback'))
44 | else:
45 | if os.path.exists("events.csv") and os.path.exists("db/events_all_2014.sqlite3"):
46 | os.remove("events.csv")
47 | os.remove("db/events_all_2014.sqlite3")
48 |
49 | http_auth = credentials.authorize(httplib2.Http())
50 | service = discovery.build('calendar', 'v3', http = http_auth)
51 | global calendarMap
52 | calendarMap = get_calendar_list_map(service)
53 |
54 | for calendarName in calendarMap:
55 | print "processing..." + calendarName + ".........."
56 | events_list = get_events_in_calendar(calendarName, calendarMap, service, '2014-01-01')
57 | write_events_to_csv(events_list)
58 |
59 | load_csv_to_db("events.csv")
60 | str = '\n'.join([calendar for calendar in calendarMap])
61 |
62 | return str
63 |
64 | @webapp.route('/oauth2callback')
65 | def oauth2callback():
66 | flow = client.flow_from_clientsecrets(
67 | 'client_secrets.json',
68 | scope='https://www.googleapis.com/auth/calendar',
69 | redirect_uri=flask.url_for('oauth2callback', _external=True))
70 |
71 | if 'code' not in flask.request.args:
72 | auth_uri = flow.step1_get_authorize_url()
73 | return flask.redirect(auth_uri)
74 | else:
75 | auth_code = flask.request.args.get('code')
76 | credentials = flow.step2_exchange(auth_code)
77 | flask.session['credentials'] = credentials.to_json()
78 | return flask.redirect(flask.url_for('finishoauth'))
79 |
80 | @webapp.route('/finishoauth')
81 | def finishoauth():
82 | return render_template('finishoauth.html')
83 |
84 | # Regular Views
85 | @webapp.route('/dbdisplay')
86 | def display():
87 | return render_template("dbdisplay.html",
88 | events = Events.query.all())
89 |
90 | @webapp.route('/dbdisplay/')
91 | def display_by_event_type(event_type):
92 | # event_type = event_type_map[event_type]
93 | return render_template("dbdisplay.html",
94 | events = Events.query.filter_by(event_type = event_type).all())
95 |
96 | @webapp.route('/dbdisplay///')
97 | def display_by_date(year, month, day):
98 | date = datetime.date(int(year), int(month), int(day))
99 | return render_template("dbdisplay.html",
100 | events = Events.query.filter_by(date = date).all())
101 |
102 | @webapp.route('/dbdisplay/duration/')
103 | def display_duration_greater_than(duration):
104 | return render_template("dbdisplay.html",
105 | events = Events.query.filter(Events.duration > float(duration)).all())
106 |
107 | @webapp.route('/distinct')
108 | def distinct():
109 | #TODO: load all the calendarNames from the db using SQLAlchemy
110 | return ''.join([event_type for event_type in event_types])
111 |
112 | # API endpoints
113 | @webapp.route('/api/all')
114 | def api_all():
115 | events = Events.query.all()
116 | return jsonify(json_list = [event.serialize for event in events])
117 |
118 | @webapp.route('/api/')
119 | def api_by_event_type(event_type):
120 | # event_type = event_type_map[event_type]
121 | events = Events.query.filter_by(event_type = event_type).all()
122 | return jsonify(json_list = [event.serialize for event in events])
123 |
124 | @webapp.route('/api///')
125 | def api_by_date(year, month, day):
126 | date = datetime.date(int(year), int(month), int(day))
127 | events = Events.query.filter_by(date = date).all()
128 | return jsonify(json_list = [event.serialize for event in events])
129 |
130 | @webapp.route('/api/duration/')
131 | def api_by_duration(duration):
132 | events = Events.query.filter(Events.duration > float(duration)).all()
133 | return jsonify(json_list = [event.serialize for event in events])
134 |
135 | # Plotting endpoints
136 | @webapp.route('/bars')
137 | def plot_d3_bars():
138 | return render_template("bars.html", calendarMap = calendarMap)
139 |
140 | @webapp.route('/calendar')
141 | def plot_d3_calendar():
142 | return render_template("calendar.html", calendarMap = calendarMap)
143 |
144 | @webapp.route('/test')
145 | def test():
146 | str = '\n'.join([calendar for calendar in calendarMap])
147 | return str
148 |
149 | if __name__ == '__main__':
150 |
151 | # EVENTS_CSV_FILE = "events.csv"
152 | # DB_FILE = "db/events_all_2014.sqlite3"
153 |
154 | # if not os.path.exists(EVENTS_CSV_FILE) and not os.path.exists(DB_FILE):
155 |
156 | # service = build_service()
157 | # calendarMap = get_calendar_list_map(service)
158 |
159 | # for calendarName in calendarMap:
160 | # events_list = get_events_in_calendar(calendarName, calendarMap, service, '2014-01-01')
161 | # write_events_to_csv(events_list)
162 |
163 | # load_csv_to_db("events.csv")
164 |
165 | webapp.debug = True
166 | webapp.run()
--------------------------------------------------------------------------------
/src/static/css/d3.tip.v0.6.3.js:
--------------------------------------------------------------------------------
1 | // d3.tip
2 | // Copyright (c) 2013 Justin Palmer
3 | //
4 | // Tooltips for d3.js SVG visualizations
5 |
6 | // Public - contructs a new tooltip
7 | //
8 | // Returns a tip
9 | d3.tip = function() {
10 | var direction = d3_tip_direction,
11 | offset = d3_tip_offset,
12 | html = d3_tip_html,
13 | node = initNode(),
14 | svg = null,
15 | point = null,
16 | target = null
17 |
18 | function tip(vis) {
19 | svg = getSVGNode(vis)
20 | point = svg.createSVGPoint()
21 | document.body.appendChild(node)
22 | }
23 |
24 | // Public - show the tooltip on the screen
25 | //
26 | // Returns a tip
27 | tip.show = function() {
28 | var args = Array.prototype.slice.call(arguments)
29 | if(args[args.length - 1] instanceof SVGElement) target = args.pop()
30 |
31 | var content = html.apply(this, args),
32 | poffset = offset.apply(this, args),
33 | dir = direction.apply(this, args),
34 | nodel = d3.select(node), i = 0,
35 | coords
36 |
37 | nodel.html(content)
38 | .style({ opacity: 1, 'pointer-events': 'all' })
39 |
40 | while(i--) nodel.classed(directions[i], false)
41 | coords = direction_callbacks.get(dir).apply(this)
42 | nodel.classed(dir, true).style({
43 | top: (coords.top + poffset[0]) + 'px',
44 | left: (coords.left + poffset[1]) + 'px'
45 | })
46 |
47 | return tip
48 | }
49 |
50 | // Public - hide the tooltip
51 | //
52 | // Returns a tip
53 | tip.hide = function() {
54 | nodel = d3.select(node)
55 | nodel.style({ opacity: 0, 'pointer-events': 'none' })
56 | return tip
57 | }
58 |
59 | // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
60 | //
61 | // n - name of the attribute
62 | // v - value of the attribute
63 | //
64 | // Returns tip or attribute value
65 | tip.attr = function(n, v) {
66 | if (arguments.length < 2 && typeof n === 'string') {
67 | return d3.select(node).attr(n)
68 | } else {
69 | var args = Array.prototype.slice.call(arguments)
70 | d3.selection.prototype.attr.apply(d3.select(node), args)
71 | }
72 |
73 | return tip
74 | }
75 |
76 | // Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
77 | //
78 | // n - name of the property
79 | // v - value of the property
80 | //
81 | // Returns tip or style property value
82 | tip.style = function(n, v) {
83 | if (arguments.length < 2 && typeof n === 'string') {
84 | return d3.select(node).style(n)
85 | } else {
86 | var args = Array.prototype.slice.call(arguments)
87 | d3.selection.prototype.style.apply(d3.select(node), args)
88 | }
89 |
90 | return tip
91 | }
92 |
93 | // Public: Set or get the direction of the tooltip
94 | //
95 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
96 | // sw(southwest), ne(northeast) or se(southeast)
97 | //
98 | // Returns tip or direction
99 | tip.direction = function(v) {
100 | if (!arguments.length) return direction
101 | direction = v == null ? v : d3.functor(v)
102 |
103 | return tip
104 | }
105 |
106 | // Public: Sets or gets the offset of the tip
107 | //
108 | // v - Array of [x, y] offset
109 | //
110 | // Returns offset or
111 | tip.offset = function(v) {
112 | if (!arguments.length) return offset
113 | offset = v == null ? v : d3.functor(v)
114 |
115 | return tip
116 | }
117 |
118 | // Public: sets or gets the html value of the tooltip
119 | //
120 | // v - String value of the tip
121 | //
122 | // Returns html value or tip
123 | tip.html = function(v) {
124 | if (!arguments.length) return html
125 | html = v == null ? v : d3.functor(v)
126 |
127 | return tip
128 | }
129 |
130 | function d3_tip_direction() { return 'n' }
131 | function d3_tip_offset() { return [0, 0] }
132 | function d3_tip_html() { return ' ' }
133 |
134 | var direction_callbacks = d3.map({
135 | n: direction_n,
136 | s: direction_s,
137 | e: direction_e,
138 | w: direction_w,
139 | nw: direction_nw,
140 | ne: direction_ne,
141 | sw: direction_sw,
142 | se: direction_se
143 | }),
144 |
145 | directions = direction_callbacks.keys()
146 |
147 | function direction_n() {
148 | var bbox = getScreenBBox()
149 | return {
150 | top: bbox.n.y - node.offsetHeight,
151 | left: bbox.n.x - node.offsetWidth / 2
152 | }
153 | }
154 |
155 | function direction_s() {
156 | var bbox = getScreenBBox()
157 | return {
158 | top: bbox.s.y,
159 | left: bbox.s.x - node.offsetWidth / 2
160 | }
161 | }
162 |
163 | function direction_e() {
164 | var bbox = getScreenBBox()
165 | return {
166 | top: bbox.e.y - node.offsetHeight / 2,
167 | left: bbox.e.x
168 | }
169 | }
170 |
171 | function direction_w() {
172 | var bbox = getScreenBBox()
173 | return {
174 | top: bbox.w.y - node.offsetHeight / 2,
175 | left: bbox.w.x - node.offsetWidth
176 | }
177 | }
178 |
179 | function direction_nw() {
180 | var bbox = getScreenBBox()
181 | return {
182 | top: bbox.nw.y - node.offsetHeight,
183 | left: bbox.nw.x - node.offsetWidth
184 | }
185 | }
186 |
187 | function direction_ne() {
188 | var bbox = getScreenBBox()
189 | return {
190 | top: bbox.ne.y - node.offsetHeight,
191 | left: bbox.ne.x
192 | }
193 | }
194 |
195 | function direction_sw() {
196 | var bbox = getScreenBBox()
197 | return {
198 | top: bbox.sw.y,
199 | left: bbox.sw.x - node.offsetWidth
200 | }
201 | }
202 |
203 | function direction_se() {
204 | var bbox = getScreenBBox()
205 | return {
206 | top: bbox.se.y,
207 | left: bbox.e.x
208 | }
209 | }
210 |
211 | function initNode() {
212 | var node = d3.select(document.createElement('div'))
213 | node.style({
214 | position: 'absolute',
215 | opacity: 0,
216 | pointerEvents: 'none',
217 | boxSizing: 'border-box'
218 | })
219 |
220 | return node.node()
221 | }
222 |
223 | function getSVGNode(el) {
224 | el = el.node()
225 | if(el.tagName.toLowerCase() == 'svg')
226 | return el
227 |
228 | return el.ownerSVGElement
229 | }
230 |
231 | // Private - gets the screen coordinates of a shape
232 | //
233 | // Given a shape on the screen, will return an SVGPoint for the directions
234 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
235 | // sw(southwest).
236 | //
237 | // +-+-+
238 | // | |
239 | // + +
240 | // | |
241 | // +-+-+
242 | //
243 | // Returns an Object {n, s, e, w, nw, sw, ne, se}
244 | function getScreenBBox() {
245 | var targetel = target || d3.event.target,
246 | bbox = {},
247 | matrix = targetel.getScreenCTM(),
248 | tbbox = targetel.getBBox(),
249 | width = tbbox.width,
250 | height = tbbox.height,
251 | x = tbbox.x,
252 | y = tbbox.y,
253 | scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
254 | scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
255 |
256 |
257 | point.x = x + scrollLeft
258 | point.y = y + scrollTop
259 | bbox.nw = point.matrixTransform(matrix)
260 | point.x += width
261 | bbox.ne = point.matrixTransform(matrix)
262 | point.y += height
263 | bbox.se = point.matrixTransform(matrix)
264 | point.x -= width
265 | bbox.sw = point.matrixTransform(matrix)
266 | point.y -= height / 2
267 | bbox.w = point.matrixTransform(matrix)
268 | point.x += width
269 | bbox.e = point.matrixTransform(matrix)
270 | point.x -= width / 2
271 | point.y -= height / 2
272 | bbox.n = point.matrixTransform(matrix)
273 | point.y += height
274 | bbox.s = point.matrixTransform(matrix)
275 |
276 | return bbox
277 | }
278 |
279 | return tip
280 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building Interactive Data Visualization using Python Flask and Google Calendar API
2 |
3 | ## TODO
4 |
5 | * In calendar.html, still need to figure out how to do data.mapValues.max
6 | * Add OAuth so other people can analyze their own calendar [Almost Done]
7 | * Deploy the app using Heroku
8 |
9 | ## OAuth Section - WIP
10 | * The OAuth flow is still not perfect, I had to always delete events.csv & db/events_all_2014.sqlite3 to restart. The branching logic is still a bit off
11 | * Right now, calendarMap is being stored as a Python global variable. Not good. I tried using cache, but I couldn't easily loop through a cache.
12 | * I think an alternative is, in each view, recompute the calendar map by writing a 'select distinct event_type from Events', but I had no success in implementing that yet in '/distinct' view
13 | * Passing in variables in a template to a included template is free, but you can use { with clause } to make it more explicit
14 | * I did some jank to convert calendarMap into a json object, so it can be used/passed down in/to .js files
15 | * I then used JQuery .foreach to create specific d3.select
16 | * I also remove the handling of white space in /api/event_type calls
17 |
18 | ## Project Milestones
19 |
20 | * **Learn about Google APIs, specifically, Calendar API**
21 | * Learn what data is queryable through Google's API documentation
22 | * Learn How to query them. e.g. what parameters to pass in, in what language
23 | * List out the questions I want to analyze (ANALYSIS.md)
24 |
25 | * **Set up a bare-minimum Flask Application**
26 | * Basic routes. e.g. index.html
27 | * Use the opportunity to review Twitter Flask Series from Simeon Franklin to get back to speed
28 |
29 | * **Modularize HTML pages using templating system JinJia**
30 | * The philosophy is to keep the business logic in route definition as simplie as possible
31 | * Remove HTML from views, put them in separate HTML files
32 | * Learn how to pass in placeholder to build static HTML dynamically
33 | * Use Template inheritance to set up 'base.html' and extends it to provide consistent theme
34 | * make views return `render_tempalte` and that is it
35 |
36 | * **Leveaging sqlite3 and perform a one time dump**
37 | * Setting up sqlite3 - nothing but a delimited text file stored in my local laptop
38 | * Quick Data Modeling on what the schema should be
39 | * Learn how to query it in Flask
40 | * Make sure all the data I care about are being dump to a .csv and to sqlite3
41 |
42 | * **ORM Database interaction using SQLAlchemy**
43 | * Again, make views as simple as possible. Hide SQL statement away from views
44 | * Learn how to query the data using SQLAlchemy constructs instead of direct SQL query
45 | * Print the raw .csv data onto the Screen using SQLAlchemy
46 | * Set up new views to render different subset of the db table
47 |
48 | * **Twitter Bootstrap**
49 | * Now that I can issue a query to backend DB and display them as raw data, beautify the webpage by Twitter bootstrap
50 | * Better layout with consistency (using template inheritance of course)
51 | * Better index.html page
52 |
53 | * **Building API endpoints**
54 | * Learn how to translate the output into JSON objects
55 | * Build views to return these JSON objects
56 | * Here you go the API endpoints
57 |
58 | * **Interactive d3 Charts**
59 | * Create Bootstrap style buttons
60 | * Create listners (e.g. d3.select.on) that takes in parameters
61 | * On click, use d3.json to hit my own API endpoints to get data
62 | * use the data and plot bars and calendars
63 |
64 | * **Build OAuth into the app so any authorized Google Calendar can use this app**
65 | * Implement OAuth
66 | * Once users grant their consent, the app stores the data into db
67 | * Update the front-end code so a list of calendarName would show up
68 | * Update the front-end code so that bar chart and calendar chart would still work
69 |
70 | ## Review of the Fundamentals of Web Application
71 | * Python
72 | * [Starting a Python Project the Right Way]
73 | * [Writing Idiomatic Python]: Also there are three vidoes on this very topic
74 | * [Import from a relative path]
75 |
76 | * Web Programming
77 | * [Web Programming Basics]: Philip Guo's intro to web programming
78 | * [What is a Web Framework?]: Given the foundation above, how web framework can help
79 | * Client-Server Interaction via HTTP protocol
80 | * The request always is initiated by the client, the server also respond with, eventually, a HTML
81 | * Web frameworks solves the nitty gritty details of routing and template-ing
82 |
83 | ## Flask Resources
84 | * [Intro to Flask with JQuery]: Philip Guo
85 | * [Flask for Data Science]
86 | * [Jeff Knupp's Flask Posts]
87 | * [Building a Flask web app with EC2, D3]
88 |
89 | ## Git References
90 | * [Resovling Git Merge Conflicts]
91 | * [How and When to Do Git Rebase]
92 | * [Uncommit a Git Commit]
93 | * [Remove directory from git but not local]
94 | * [Use .gitignore to ignore checking in certain files]
95 |
96 | ## D3 References
97 | * [General Update Pattern]
98 | * [General Update Pattern III]
99 | * [d3 nest]
100 |
101 | ## Google API References
102 | * [QPX Express API]: Global airline pricing and shopping in a single, standard API.
103 | * [Google Calendar API]
104 |
105 | ## Using Grunt to do Front-End Web Development
106 | * [Browser Auto-refresh]
107 | * [Another example]
108 |
109 | ## Heroku
110 | * [Python Boilerplate] - Deploy to Heroku section
111 | * [Getting started with Python in Heroku]
112 |
113 | ## OAuth on Flask
114 | * [Using Google OAuth2 on Flask]
115 | * [OAuth 2.0 overview]
116 | * [Google API client library python]
117 | * [Using OAuth2.0 for web application python]
118 |
119 | ## JQuery
120 | * [Select id with space]
121 |
122 | Another new thing I am doing is to paste the references I found right next to the code I wrote down to solve a particular problem.
123 |
124 | [question]: https://www.quora.com/As-a-data-scientist-what-are-the-things-that-I-can-learn-from-full-stack-developers-so-that-I-can-build-interesting-web-applications-for-data-science
125 |
126 | [Starting a Python Project the Right Way]: http://www.jeffknupp.com/blog/2014/02/04/starting-a-python-project-the-right-way/
127 | [Writing Idiomatic Python]: https://speakerdeck.com/nycpython/writing-idiomatic-python-jeff-knupp
128 | [Import from a relative path]:http://stackoverflow.com/questions/279237/import-a-module-from-a-relative-path
129 |
130 | [Web Programming Basics]: http://www.pgbovine.net/teaching-web-programming.htm
131 | [What is a Web Framework?]: http://www.jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/
132 | [Intro to Flask with JQuery]: http://www.pgbovine.net/flask-python-tutorial.htm
133 | [Flask for Data Science]: http://www.datacommunitydc.org/blog/2014/02/flask-mega-meta-tutorial-data-scientists
134 | [Jeff Knupp's Flask Posts]: http://www.jeffknupp.com/blog/categories/flask/
135 | [Building a Flask web app with EC2, D3]: http://www.datasciencebytes.com/bytes/2015/03/07/a-d3js-plot-powered-by-a-sql-database/
136 |
137 | [Resovling Git Merge Conflicts]: https://help.github.com/articles/resolving-a-merge-conflict-from-the-command-line/
138 | [How and When to Do Git Rebase]: https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase/
139 | [Uncommit a Git Commit]: http://stackoverflow.com/questions/2845731/how-to-uncommit-my-last-commit-in-git
140 | [Remove directory from git but not local]: http://stackoverflow.com/questions/6313126/how-to-remove-a-directory-in-my-github-repository
141 | [Use .gitignore to ignore checking in certain files]:https://help.github.com/articles/ignoring-files/
142 |
143 | [QPX Express API]: https://developers.google.com/qpx-express/
144 | [Google Calendar API]: https://developers.google.com/google-apps/calendar/
145 |
146 | [Browser Auto-refresh]: http://stackoverflow.com/questions/21913363/why-isnt-grunt-contrib-watch-livereload-working
147 | [Another example]: http://justinklemm.com/grunt-watch-livereload-javascript-less-sass-compilation/
148 |
149 | [General Update Pattern]: http://bl.ocks.org/mbostock/3808218
150 | [General Update Pattern III]: http://bl.ocks.org/mbostock/3808234
151 | [d3 nest]: http://bl.ocks.org/phoebebright/raw/3176159/
152 |
153 | [Python Boilerplate]: https://github.com/mjhea0/flask-boilerplate
154 | [Getting started with Python in Heroku]: https://devcenter.heroku.com/articles/getting-started-with-python#next-steps
155 |
156 | [Using Google OAuth2 on Flask]: https://github.com/mimming/python-flask-google-api-starter
157 | [OAuth 2.0 overview]: https://developers.google.com/identity/protocols/OAuth2
158 | [Google API client library python]: https://developers.google.com/api-client-library/python/guide/aaa_oauth
159 | [Using OAuth2.0 for web application python]: https://developers.google.com/api-client-library/python/auth/web-app
160 |
161 | [Select id with space]: http://stackoverflow.com/questions/596314/jquery-ids-with-spaces
--------------------------------------------------------------------------------
/src/static/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.2.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-o-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#2d6ca2));background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-primary:disabled,.btn-primary[disabled]{background-color:#2d6ca2;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f3f3f3));background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-o-linear-gradient(top,#222 0,#282828 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#222),to(#282828));background-image:linear-gradient(to bottom,#222 0,#282828 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-o-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3071a9));background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-o-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3278b3));background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);background-repeat:repeat-x;border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
--------------------------------------------------------------------------------
/src/static/css/bootstrap-theme.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"bootstrap-theme.css","sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAeA;;;;;;EAME,0CAAA;EC+CA,6FAAA;EACQ,qFAAA;EC5DT;AFiBC;;;;;;;;;;;;EC0CA,0DAAA;EACQ,kDAAA;EC7CT;AFqCC;;EAEE,wBAAA;EEnCH;AFwCD;EG/CI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EA+B2C,2BAAA;EAA2B,oBAAA;EE7BvE;AFAC;;EAEE,2BAAA;EACA,8BAAA;EEEH;AFCC;;EAEE,2BAAA;EACA,uBAAA;EECH;AFEC;;EAEE,2BAAA;EACA,wBAAA;EEAH;AFeD;EGhDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0BD;AFxBC;;EAEE,2BAAA;EACA,8BAAA;EE0BH;AFvBC;;EAEE,2BAAA;EACA,uBAAA;EEyBH;AFtBC;;EAEE,2BAAA;EACA,wBAAA;EEwBH;AFRD;EGjDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EEkDD;AFhDC;;EAEE,2BAAA;EACA,8BAAA;EEkDH;AF/CC;;EAEE,2BAAA;EACA,uBAAA;EEiDH;AF9CC;;EAEE,2BAAA;EACA,wBAAA;EEgDH;AF/BD;EGlDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0ED;AFxEC;;EAEE,2BAAA;EACA,8BAAA;EE0EH;AFvEC;;EAEE,2BAAA;EACA,uBAAA;EEyEH;AFtEC;;EAEE,2BAAA;EACA,wBAAA;EEwEH;AFtDD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EEkGD;AFhGC;;EAEE,2BAAA;EACA,8BAAA;EEkGH;AF/FC;;EAEE,2BAAA;EACA,uBAAA;EEiGH;AF9FC;;EAEE,2BAAA;EACA,wBAAA;EEgGH;AF7ED;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJ8BA,6BAAA;EACA,uBAAA;EE0HD;AFxHC;;EAEE,2BAAA;EACA,8BAAA;EE0HH;AFvHC;;EAEE,2BAAA;EACA,uBAAA;EEyHH;AFtHC;;EAEE,2BAAA;EACA,wBAAA;EEwHH;AF7FD;;ECbE,oDAAA;EACQ,4CAAA;EC8GT;AFvFD;;EGvEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHsEF,2BAAA;EE6FD;AF3FD;;;EG5EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4EF,2BAAA;EEiGD;AFvFD;EG1FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ4GA,oBAAA;EC9CA,6FAAA;EACQ,qFAAA;EC4IT;AFlGD;EG1FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECqJT;AF/FD;;EAEE,gDAAA;EEiGD;AF7FD;EG5GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EFgOD;AFrGD;EG5GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0KT;AF9GD;;EAWI,2CAAA;EEuGH;AFlGD;;;EAGE,kBAAA;EEoGD;AF1FD;EACE,+CAAA;EC3FA,4FAAA;EACQ,oFAAA;ECwLT;AFlFD;EGtJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EE8FD;AFzFD;EGvJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EEsGD;AFhGD;EGxJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EE8GD;AFvGD;EGzJI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8IF,uBAAA;EEsHD;AFtGD;EGlKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2QH;AFnGD;EG5KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkRH;AFzGD;EG7KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDyRH;AF/GD;EG9KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDgSH;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AF3HD;EGhLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AF9HD;EGnJI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDoRH;AF1HD;EACE,oBAAA;EC/IA,oDAAA;EACQ,4CAAA;EC4QT;AF3HD;;;EAGE,+BAAA;EGpME,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHkMF,uBAAA;EEiID;AFvHD;ECjKE,mDAAA;EACQ,2CAAA;EC2RT;AFjHD;EG1NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8UH;AFvHD;EG3NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqVH;AF7HD;EG5NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4VH;AFnID;EG7NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmWH;AFzID;EG9NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0WH;AF/ID;EG/NI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDiXH;AF9ID;EGvOI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHqOF,uBAAA;EC1LA,2FAAA;EACQ,mFAAA;EC+UT","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-bg, 5%); @end-color: darken(@navbar-default-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-bg; @end-color: lighten(@navbar-inverse-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n}\n\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n &::-moz-placeholder { color: @color; // Firefox\n opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",null,"// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]}
--------------------------------------------------------------------------------
/src/static/css/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.2.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | .btn-default,
8 | .btn-primary,
9 | .btn-success,
10 | .btn-info,
11 | .btn-warning,
12 | .btn-danger {
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
16 | }
17 | .btn-default:active,
18 | .btn-primary:active,
19 | .btn-success:active,
20 | .btn-info:active,
21 | .btn-warning:active,
22 | .btn-danger:active,
23 | .btn-default.active,
24 | .btn-primary.active,
25 | .btn-success.active,
26 | .btn-info.active,
27 | .btn-warning.active,
28 | .btn-danger.active {
29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
31 | }
32 | .btn:active,
33 | .btn.active {
34 | background-image: none;
35 | }
36 | .btn-default {
37 | text-shadow: 0 1px 0 #fff;
38 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
39 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
40 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
41 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
43 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
44 | background-repeat: repeat-x;
45 | border-color: #dbdbdb;
46 | border-color: #ccc;
47 | }
48 | .btn-default:hover,
49 | .btn-default:focus {
50 | background-color: #e0e0e0;
51 | background-position: 0 -15px;
52 | }
53 | .btn-default:active,
54 | .btn-default.active {
55 | background-color: #e0e0e0;
56 | border-color: #dbdbdb;
57 | }
58 | .btn-default:disabled,
59 | .btn-default[disabled] {
60 | background-color: #e0e0e0;
61 | background-image: none;
62 | }
63 | .btn-primary {
64 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
65 | background-image: -o-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
66 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#2d6ca2));
67 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
68 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
69 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
70 | background-repeat: repeat-x;
71 | border-color: #2b669a;
72 | }
73 | .btn-primary:hover,
74 | .btn-primary:focus {
75 | background-color: #2d6ca2;
76 | background-position: 0 -15px;
77 | }
78 | .btn-primary:active,
79 | .btn-primary.active {
80 | background-color: #2d6ca2;
81 | border-color: #2b669a;
82 | }
83 | .btn-primary:disabled,
84 | .btn-primary[disabled] {
85 | background-color: #2d6ca2;
86 | background-image: none;
87 | }
88 | .btn-success {
89 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
90 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
91 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
92 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
93 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
94 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
95 | background-repeat: repeat-x;
96 | border-color: #3e8f3e;
97 | }
98 | .btn-success:hover,
99 | .btn-success:focus {
100 | background-color: #419641;
101 | background-position: 0 -15px;
102 | }
103 | .btn-success:active,
104 | .btn-success.active {
105 | background-color: #419641;
106 | border-color: #3e8f3e;
107 | }
108 | .btn-success:disabled,
109 | .btn-success[disabled] {
110 | background-color: #419641;
111 | background-image: none;
112 | }
113 | .btn-info {
114 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
115 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
116 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
117 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
118 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
119 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
120 | background-repeat: repeat-x;
121 | border-color: #28a4c9;
122 | }
123 | .btn-info:hover,
124 | .btn-info:focus {
125 | background-color: #2aabd2;
126 | background-position: 0 -15px;
127 | }
128 | .btn-info:active,
129 | .btn-info.active {
130 | background-color: #2aabd2;
131 | border-color: #28a4c9;
132 | }
133 | .btn-info:disabled,
134 | .btn-info[disabled] {
135 | background-color: #2aabd2;
136 | background-image: none;
137 | }
138 | .btn-warning {
139 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
140 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
141 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
142 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
143 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
144 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
145 | background-repeat: repeat-x;
146 | border-color: #e38d13;
147 | }
148 | .btn-warning:hover,
149 | .btn-warning:focus {
150 | background-color: #eb9316;
151 | background-position: 0 -15px;
152 | }
153 | .btn-warning:active,
154 | .btn-warning.active {
155 | background-color: #eb9316;
156 | border-color: #e38d13;
157 | }
158 | .btn-warning:disabled,
159 | .btn-warning[disabled] {
160 | background-color: #eb9316;
161 | background-image: none;
162 | }
163 | .btn-danger {
164 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
165 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
166 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
167 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
168 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
169 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
170 | background-repeat: repeat-x;
171 | border-color: #b92c28;
172 | }
173 | .btn-danger:hover,
174 | .btn-danger:focus {
175 | background-color: #c12e2a;
176 | background-position: 0 -15px;
177 | }
178 | .btn-danger:active,
179 | .btn-danger.active {
180 | background-color: #c12e2a;
181 | border-color: #b92c28;
182 | }
183 | .btn-danger:disabled,
184 | .btn-danger[disabled] {
185 | background-color: #c12e2a;
186 | background-image: none;
187 | }
188 | .thumbnail,
189 | .img-thumbnail {
190 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
191 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
192 | }
193 | .dropdown-menu > li > a:hover,
194 | .dropdown-menu > li > a:focus {
195 | background-color: #e8e8e8;
196 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
197 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
198 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
199 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
200 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
201 | background-repeat: repeat-x;
202 | }
203 | .dropdown-menu > .active > a,
204 | .dropdown-menu > .active > a:hover,
205 | .dropdown-menu > .active > a:focus {
206 | background-color: #357ebd;
207 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
208 | background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
209 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
210 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
211 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
212 | background-repeat: repeat-x;
213 | }
214 | .navbar-default {
215 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
216 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
217 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
218 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
220 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
221 | background-repeat: repeat-x;
222 | border-radius: 4px;
223 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
224 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
225 | }
226 | .navbar-default .navbar-nav > .active > a {
227 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
228 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
229 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f3f3f3));
230 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
231 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
232 | background-repeat: repeat-x;
233 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
234 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
235 | }
236 | .navbar-brand,
237 | .navbar-nav > li > a {
238 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
239 | }
240 | .navbar-inverse {
241 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
242 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
243 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
244 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
245 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
246 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
247 | background-repeat: repeat-x;
248 | }
249 | .navbar-inverse .navbar-nav > .active > a {
250 | background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
251 | background-image: -o-linear-gradient(top, #222 0%, #282828 100%);
252 | background-image: -webkit-gradient(linear, left top, left bottom, from(#222), to(#282828));
253 | background-image: linear-gradient(to bottom, #222 0%, #282828 100%);
254 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
255 | background-repeat: repeat-x;
256 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
257 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
258 | }
259 | .navbar-inverse .navbar-brand,
260 | .navbar-inverse .navbar-nav > li > a {
261 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
262 | }
263 | .navbar-static-top,
264 | .navbar-fixed-top,
265 | .navbar-fixed-bottom {
266 | border-radius: 0;
267 | }
268 | .alert {
269 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
270 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
271 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
272 | }
273 | .alert-success {
274 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
275 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
276 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
277 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
278 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
279 | background-repeat: repeat-x;
280 | border-color: #b2dba1;
281 | }
282 | .alert-info {
283 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
284 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
285 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
286 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
287 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
288 | background-repeat: repeat-x;
289 | border-color: #9acfea;
290 | }
291 | .alert-warning {
292 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
293 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
294 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
295 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
296 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
297 | background-repeat: repeat-x;
298 | border-color: #f5e79e;
299 | }
300 | .alert-danger {
301 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
302 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
303 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
304 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
305 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
306 | background-repeat: repeat-x;
307 | border-color: #dca7a7;
308 | }
309 | .progress {
310 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
311 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
312 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
313 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
314 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
315 | background-repeat: repeat-x;
316 | }
317 | .progress-bar {
318 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
319 | background-image: -o-linear-gradient(top, #428bca 0%, #3071a9 100%);
320 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3071a9));
321 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
322 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
323 | background-repeat: repeat-x;
324 | }
325 | .progress-bar-success {
326 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
327 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
328 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
329 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
330 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
331 | background-repeat: repeat-x;
332 | }
333 | .progress-bar-info {
334 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
335 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
336 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
337 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
338 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
339 | background-repeat: repeat-x;
340 | }
341 | .progress-bar-warning {
342 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
343 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
344 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
345 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
346 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
347 | background-repeat: repeat-x;
348 | }
349 | .progress-bar-danger {
350 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
351 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
352 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
353 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
354 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
355 | background-repeat: repeat-x;
356 | }
357 | .progress-bar-striped {
358 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
359 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
360 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
361 | }
362 | .list-group {
363 | border-radius: 4px;
364 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
365 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
366 | }
367 | .list-group-item.active,
368 | .list-group-item.active:hover,
369 | .list-group-item.active:focus {
370 | text-shadow: 0 -1px 0 #3071a9;
371 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
372 | background-image: -o-linear-gradient(top, #428bca 0%, #3278b3 100%);
373 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3278b3));
374 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
375 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
376 | background-repeat: repeat-x;
377 | border-color: #3278b3;
378 | }
379 | .panel {
380 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
381 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
382 | }
383 | .panel-default > .panel-heading {
384 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
385 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
386 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
387 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
388 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
389 | background-repeat: repeat-x;
390 | }
391 | .panel-primary > .panel-heading {
392 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
393 | background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
394 | background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
395 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
396 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
397 | background-repeat: repeat-x;
398 | }
399 | .panel-success > .panel-heading {
400 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
401 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
403 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
405 | background-repeat: repeat-x;
406 | }
407 | .panel-info > .panel-heading {
408 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
409 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
410 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
411 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
412 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
413 | background-repeat: repeat-x;
414 | }
415 | .panel-warning > .panel-heading {
416 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
417 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
418 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
419 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
420 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
421 | background-repeat: repeat-x;
422 | }
423 | .panel-danger > .panel-heading {
424 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
425 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
426 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
427 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
428 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
429 | background-repeat: repeat-x;
430 | }
431 | .well {
432 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
433 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
435 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
437 | background-repeat: repeat-x;
438 | border-color: #dcdcdc;
439 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
440 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
441 | }
442 | /*# sourceMappingURL=bootstrap-theme.css.map */
443 |
--------------------------------------------------------------------------------
/src/static/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.2.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;e?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(150):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var f=function(){c.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",f).emulateTransitionEnd(150):f()}else b&&b()},c.prototype.checkScrollbar=function(){document.body.clientWidth>=window.innerWidth||(this.scrollbarWidth=this.scrollbarWidth||this.measureScrollbar())},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.scrollbarWidth&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.2.0",c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var c=a.contains(document.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!c)return;var d=this,e=this.tip(),f=this.getUID(this.type);this.setContent(),e.attr("id",f),this.$element.attr("aria-describedby",f),this.options.animation&&e.addClass("fade");var g="function"==typeof this.options.placement?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,i=h.test(g);i&&(g=g.replace(h,"")||"top"),e.detach().css({top:0,left:0,display:"block"}).addClass(g).data("bs."+this.type,this),this.options.container?e.appendTo(this.options.container):e.insertAfter(this.$element);var j=this.getPosition(),k=e[0].offsetWidth,l=e[0].offsetHeight;if(i){var m=g,n=this.$element.parent(),o=this.getPosition(n);g="bottom"==g&&j.top+j.height+l-o.scroll>o.height?"top":"top"==g&&j.top-o.scroll-l<0?"bottom":"right"==g&&j.right+k>o.width?"left":"left"==g&&j.left-kg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){clearTimeout(this.timeout),this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.2.0",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").empty()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.2.0",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.2.0",c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.closest("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},c.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one("bsTransitionEnd",e).emulateTransitionEnd(150):e(),f.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(c){c.preventDefault(),b.call(a(this),"show")})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.2.0",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=a(document).height(),d=this.$target.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=b-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){null!=this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:b-this.$element.height()-h}))}}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},d.offsetBottom&&(d.offset.bottom=d.offsetBottom),d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/src/static/fonts/glyphicons-halflings-regular.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------