mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2025-07-03 19:23:17 +00:00
Compare commits
20 Commits
access-pip
...
cbd0a41bce
Author | SHA1 | Date | |
---|---|---|---|
cbd0a41bce | |||
c68286e010 | |||
4a29a52f2a | |||
991810cff5 | |||
6025a4a606 | |||
e1cfd394fa | |||
882987ba68 | |||
a03b5918d9 | |||
43b38b2216 | |||
543276d766 | |||
485a0155c6 | |||
c29c50feb9 | |||
c191e7bd4a | |||
8f960cf359 | |||
ccf484c9bc | |||
d0d2a8abd6 | |||
03876f6a39 | |||
cdf6f9fcfd | |||
268da220d2 | |||
84e1755a57 |
@ -5,9 +5,9 @@
|
|||||||
!app
|
!app
|
||||||
!migrations
|
!migrations
|
||||||
!tests
|
!tests
|
||||||
!.flaskenv
|
|
||||||
!boot.sh
|
!boot.sh
|
||||||
!config.py
|
!config.py
|
||||||
!docker-nopaque-entrypoint.sh
|
!docker-nopaque-entrypoint.sh
|
||||||
!nopaque.py
|
|
||||||
!requirements.txt
|
!requirements.txt
|
||||||
|
!requirements.freezed.txt
|
||||||
|
!wsgi.py
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -35,19 +35,17 @@ ENV PATH="${NOPAQUE_PYTHON3_VENV_PATH}/bin:${PATH}"
|
|||||||
|
|
||||||
|
|
||||||
# Install Python dependencies
|
# Install Python dependencies
|
||||||
COPY --chown=nopaque:nopaque requirements.txt requirements.txt
|
COPY --chown=nopaque:nopaque requirements.freezed.txt requirements.freezed.txt
|
||||||
RUN python3 -m pip install --requirement requirements.txt \
|
RUN python3 -m pip install --requirement requirements.freezed.txt \
|
||||||
&& rm requirements.txt
|
&& rm requirements.freezed.txt
|
||||||
|
|
||||||
|
|
||||||
# Install the application
|
# Install the application
|
||||||
COPY docker-nopaque-entrypoint.sh /usr/local/bin/
|
COPY docker-nopaque-entrypoint.sh /usr/local/bin/
|
||||||
|
|
||||||
COPY --chown=nopaque:nopaque app app
|
COPY --chown=nopaque:nopaque app app
|
||||||
COPY --chown=nopaque:nopaque migrations migrations
|
COPY --chown=nopaque:nopaque migrations migrations
|
||||||
COPY --chown=nopaque:nopaque tests tests
|
COPY --chown=nopaque:nopaque tests tests
|
||||||
COPY --chown=nopaque:nopaque .flaskenv boot.sh config.py nopaque.py requirements.txt ./
|
COPY --chown=nopaque:nopaque boot.sh config.py wsgi.py ./
|
||||||
|
|
||||||
RUN mkdir logs
|
RUN mkdir logs
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ from docker import DockerClient
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_apscheduler import APScheduler
|
from flask_apscheduler import APScheduler
|
||||||
from flask_assets import Environment
|
from flask_assets import Environment
|
||||||
from flask_breadcrumbs import Breadcrumbs, default_breadcrumb_root
|
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
from flask_marshmallow import Marshmallow
|
from flask_marshmallow import Marshmallow
|
||||||
@ -17,7 +16,6 @@ from flask_hashids import Hashids
|
|||||||
|
|
||||||
apifairy = APIFairy()
|
apifairy = APIFairy()
|
||||||
assets = Environment()
|
assets = Environment()
|
||||||
breadcrumbs = Breadcrumbs()
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
docker_client = DockerClient()
|
docker_client = DockerClient()
|
||||||
hashids = Hashids()
|
hashids = Hashids()
|
||||||
@ -33,6 +31,9 @@ scheduler = APScheduler()
|
|||||||
socketio = SocketIO()
|
socketio = SocketIO()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Create export for lemmatized corpora
|
||||||
|
|
||||||
|
|
||||||
def create_app(config: Config = Config) -> Flask:
|
def create_app(config: Config = Config) -> Flask:
|
||||||
''' Creates an initialized Flask (WSGI Application) object. '''
|
''' Creates an initialized Flask (WSGI Application) object. '''
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@ -46,7 +47,6 @@ def create_app(config: Config = Config) -> Flask:
|
|||||||
|
|
||||||
apifairy.init_app(app)
|
apifairy.init_app(app)
|
||||||
assets.init_app(app)
|
assets.init_app(app)
|
||||||
breadcrumbs.init_app(app)
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
hashids.init_app(app)
|
hashids.init_app(app)
|
||||||
login.init_app(app)
|
login.init_app(app)
|
||||||
@ -61,23 +61,19 @@ def create_app(config: Config = Config) -> Flask:
|
|||||||
register_event_listeners()
|
register_event_listeners()
|
||||||
|
|
||||||
from .admin import bp as admin_blueprint
|
from .admin import bp as admin_blueprint
|
||||||
default_breadcrumb_root(admin_blueprint, '.admin')
|
|
||||||
app.register_blueprint(admin_blueprint, url_prefix='/admin')
|
app.register_blueprint(admin_blueprint, url_prefix='/admin')
|
||||||
|
|
||||||
from .api import bp as api_blueprint
|
from .api import bp as api_blueprint
|
||||||
app.register_blueprint(api_blueprint, url_prefix='/api')
|
app.register_blueprint(api_blueprint, url_prefix='/api')
|
||||||
|
|
||||||
from .auth import bp as auth_blueprint
|
from .auth import bp as auth_blueprint
|
||||||
default_breadcrumb_root(auth_blueprint, '.')
|
|
||||||
app.register_blueprint(auth_blueprint)
|
app.register_blueprint(auth_blueprint)
|
||||||
|
|
||||||
from .contributions import bp as contributions_blueprint
|
from .contributions import bp as contributions_blueprint
|
||||||
default_breadcrumb_root(contributions_blueprint, '.contributions')
|
|
||||||
app.register_blueprint(contributions_blueprint, url_prefix='/contributions')
|
app.register_blueprint(contributions_blueprint, url_prefix='/contributions')
|
||||||
|
|
||||||
from .corpora import bp as corpora_blueprint
|
from .corpora import bp as corpora_blueprint
|
||||||
from .corpora.cqi_over_sio import CQiNamespace
|
from .corpora.cqi_over_sio import CQiNamespace
|
||||||
default_breadcrumb_root(corpora_blueprint, '.corpora')
|
|
||||||
app.register_blueprint(corpora_blueprint, cli_group='corpus', url_prefix='/corpora')
|
app.register_blueprint(corpora_blueprint, cli_group='corpus', url_prefix='/corpora')
|
||||||
socketio.on_namespace(CQiNamespace('/cqi_over_sio'))
|
socketio.on_namespace(CQiNamespace('/cqi_over_sio'))
|
||||||
|
|
||||||
@ -85,23 +81,18 @@ def create_app(config: Config = Config) -> Flask:
|
|||||||
app.register_blueprint(errors_bp)
|
app.register_blueprint(errors_bp)
|
||||||
|
|
||||||
from .jobs import bp as jobs_blueprint
|
from .jobs import bp as jobs_blueprint
|
||||||
default_breadcrumb_root(jobs_blueprint, '.jobs')
|
|
||||||
app.register_blueprint(jobs_blueprint, url_prefix='/jobs')
|
app.register_blueprint(jobs_blueprint, url_prefix='/jobs')
|
||||||
|
|
||||||
from .main import bp as main_blueprint
|
from .main import bp as main_blueprint
|
||||||
default_breadcrumb_root(main_blueprint, '.')
|
|
||||||
app.register_blueprint(main_blueprint, cli_group=None)
|
app.register_blueprint(main_blueprint, cli_group=None)
|
||||||
|
|
||||||
from .services import bp as services_blueprint
|
from .services import bp as services_blueprint
|
||||||
default_breadcrumb_root(services_blueprint, '.services')
|
|
||||||
app.register_blueprint(services_blueprint, url_prefix='/services')
|
app.register_blueprint(services_blueprint, url_prefix='/services')
|
||||||
|
|
||||||
from .settings import bp as settings_blueprint
|
from .settings import bp as settings_blueprint
|
||||||
default_breadcrumb_root(settings_blueprint, '.settings')
|
|
||||||
app.register_blueprint(settings_blueprint, url_prefix='/settings')
|
app.register_blueprint(settings_blueprint, url_prefix='/settings')
|
||||||
|
|
||||||
from .users import bp as users_blueprint
|
from .users import bp as users_blueprint
|
||||||
default_breadcrumb_root(users_blueprint, '.users')
|
|
||||||
app.register_blueprint(users_blueprint, cli_group='user', url_prefix='/users')
|
app.register_blueprint(users_blueprint, cli_group='user', url_prefix='/users')
|
||||||
|
|
||||||
from .workshops import bp as workshops_blueprint
|
from .workshops import bp as workshops_blueprint
|
||||||
|
49
app/admin/events.py
Normal file
49
app/admin/events.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import disconnect, Namespace
|
||||||
|
from app import db, hashids
|
||||||
|
from app.decorators import socketio_admin_required
|
||||||
|
from app.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class AdminNamespace(Namespace):
|
||||||
|
def on_connect(self):
|
||||||
|
# Check if the user is authenticated and is an administrator
|
||||||
|
if not (current_user.is_authenticated and current_user.is_administrator):
|
||||||
|
disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
@socketio_admin_required
|
||||||
|
def on_set_user_confirmed(self, user_hashid: str, confirmed_value: bool):
|
||||||
|
# Decode the user hashid
|
||||||
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
|
# Validate user_id
|
||||||
|
if not isinstance(user_id, int):
|
||||||
|
return {
|
||||||
|
'code': 400,
|
||||||
|
'body': 'user_id is invalid'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate confirmed_value
|
||||||
|
if not isinstance(confirmed_value, bool):
|
||||||
|
return {
|
||||||
|
'code': 400,
|
||||||
|
'body': 'confirmed_value is invalid'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load user from database
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
if user is None:
|
||||||
|
return {
|
||||||
|
'code': 404,
|
||||||
|
'body': 'User not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update user confirmed status
|
||||||
|
user.confirmed = confirmed_value
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'code': 200,
|
||||||
|
'body': f'User "{user.username}" is now {"confirmed" if confirmed_value else "unconfirmed"}'
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
from flask import abort, request
|
from flask import abort, request
|
||||||
from app import db
|
|
||||||
from app.decorators import content_negotiation
|
from app.decorators import content_negotiation
|
||||||
|
from app import db
|
||||||
from app.models import User
|
from app.models import User
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from flask import abort, flash, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from app import db, hashids
|
from app import db, hashids
|
||||||
from app.models import Avatar, Corpus, Role, User
|
from app.models import Avatar, Corpus, Role, User
|
||||||
from app.users.settings.forms import (
|
from app.users.settings.forms import (
|
||||||
@ -11,14 +10,9 @@ from app.users.settings.forms import (
|
|||||||
)
|
)
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import UpdateUserForm
|
from .forms import UpdateUserForm
|
||||||
from app.users.utils import (
|
|
||||||
user_endpoint_arguments_constructor as user_eac,
|
|
||||||
user_dynamic_list_constructor as user_dlc
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('')
|
@bp.route('')
|
||||||
@register_breadcrumb(bp, '.', '<i class="material-icons left">admin_panel_settings</i>Administration')
|
|
||||||
def admin():
|
def admin():
|
||||||
return render_template(
|
return render_template(
|
||||||
'admin/admin.html.j2',
|
'admin/admin.html.j2',
|
||||||
@ -27,7 +21,6 @@ def admin():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/corpora')
|
@bp.route('/corpora')
|
||||||
@register_breadcrumb(bp, '.corpora', 'Corpora')
|
|
||||||
def corpora():
|
def corpora():
|
||||||
corpora = Corpus.query.all()
|
corpora = Corpus.query.all()
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -38,7 +31,6 @@ def corpora():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/users')
|
@bp.route('/users')
|
||||||
@register_breadcrumb(bp, '.users', '<i class="material-icons left">group</i>Users')
|
|
||||||
def users():
|
def users():
|
||||||
users = User.query.all()
|
users = User.query.all()
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -49,7 +41,6 @@ def users():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/users/<hashid:user_id>')
|
@bp.route('/users/<hashid:user_id>')
|
||||||
@register_breadcrumb(bp, '.users.entity', '', dynamic_list_constructor=user_dlc)
|
|
||||||
def user(user_id):
|
def user(user_id):
|
||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
corpora = Corpus.query.filter(Corpus.user == user).all()
|
corpora = Corpus.query.filter(Corpus.user == user).all()
|
||||||
@ -62,7 +53,6 @@ def user(user_id):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/users/<hashid:user_id>/settings', methods=['GET', 'POST'])
|
@bp.route('/users/<hashid:user_id>/settings', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.users.entity.settings', '<i class="material-icons left">settings</i>Settings')
|
|
||||||
def user_settings(user_id):
|
def user_settings(user_id):
|
||||||
user = User.query.get_or_404(user_id)
|
user = User.query.get_or_404(user_id)
|
||||||
update_account_information_form = UpdateAccountInformationForm(user)
|
update_account_information_form = UpdateAccountInformationForm(user)
|
||||||
|
@ -5,8 +5,8 @@ from flask import abort, Blueprint
|
|||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
from app import db, hashids
|
from app import db, hashids
|
||||||
from app.models import Job, JobInput, JobStatus, TesseractOCRPipelineModel
|
from app.models import Job, JobInput, JobStatus, TesseractOCRPipelineModel
|
||||||
from .schemas import EmptySchema, JobSchema, SpaCyNLPPipelineJobSchema, TesseractOCRPipelineJobSchema, TesseractOCRPipelineModelSchema
|
|
||||||
from .auth import auth_error_responses, token_auth
|
from .auth import auth_error_responses, token_auth
|
||||||
|
from .schemas import EmptySchema, JobSchema, SpaCyNLPPipelineJobSchema, TesseractOCRPipelineJobSchema, TesseractOCRPipelineModelSchema
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('jobs', __name__)
|
bp = Blueprint('jobs', __name__)
|
||||||
@ -77,7 +77,7 @@ def delete_job(job_id):
|
|||||||
job = Job.query.get(job_id)
|
job = Job.query.get(job_id)
|
||||||
if job is None:
|
if job is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
try:
|
try:
|
||||||
job.delete()
|
job.delete()
|
||||||
@ -97,6 +97,6 @@ def get_job(job_id):
|
|||||||
job = Job.query.get(job_id)
|
job = Job.query.get(job_id)
|
||||||
if job is None:
|
if job is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return job
|
return job
|
||||||
|
@ -3,11 +3,11 @@ from apifairy import authenticate, body, response
|
|||||||
from apifairy.decorators import other_responses
|
from apifairy.decorators import other_responses
|
||||||
from flask import abort, Blueprint
|
from flask import abort, Blueprint
|
||||||
from werkzeug.exceptions import InternalServerError
|
from werkzeug.exceptions import InternalServerError
|
||||||
from app import db
|
|
||||||
from app.email import create_message, send
|
from app.email import create_message, send
|
||||||
|
from app import db
|
||||||
from app.models import User
|
from app.models import User
|
||||||
from .schemas import EmptySchema, UserSchema
|
|
||||||
from .auth import auth_error_responses, token_auth
|
from .auth import auth_error_responses, token_auth
|
||||||
|
from .schemas import EmptySchema, UserSchema
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('users', __name__)
|
bp = Blueprint('users', __name__)
|
||||||
@ -60,7 +60,7 @@ def delete_user(user_id):
|
|||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
if user is None:
|
if user is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if not (user == current_user or current_user.is_administrator()):
|
if not (user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
user.delete()
|
user.delete()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -78,7 +78,7 @@ def get_user(user_id):
|
|||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
if user is None:
|
if user is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if not (user == current_user or current_user.is_administrator()):
|
if not (user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@ -94,6 +94,6 @@ def get_user_by_username(username):
|
|||||||
user = User.query.filter(User.username == username).first()
|
user = User.query.filter(User.username == username).first()
|
||||||
if user is None:
|
if user is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
if not (user == current_user or current_user.is_administrator()):
|
if not (user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return user
|
return user
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from flask import abort, flash, redirect, render_template, request, url_for
|
from flask import abort, flash, redirect, render_template, request, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user, login_user, login_required, logout_user
|
from flask_login import current_user, login_user, login_required, logout_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.email import create_message, send
|
from app.email import create_message, send
|
||||||
@ -19,18 +18,21 @@ def before_request():
|
|||||||
Checks if a user is unconfirmed when visiting specific sites. Redirects to
|
Checks if a user is unconfirmed when visiting specific sites. Redirects to
|
||||||
unconfirmed view if user is unconfirmed.
|
unconfirmed view if user is unconfirmed.
|
||||||
"""
|
"""
|
||||||
if current_user.is_authenticated:
|
if not current_user.is_authenticated:
|
||||||
current_user.ping()
|
return
|
||||||
db.session.commit()
|
|
||||||
if (not current_user.confirmed
|
current_user.ping()
|
||||||
and request.endpoint
|
db.session.commit()
|
||||||
and request.blueprint != 'auth'
|
if (not current_user.confirmed
|
||||||
and request.endpoint != 'static'):
|
and request.endpoint
|
||||||
return redirect(url_for('auth.unconfirmed'))
|
and request.blueprint != 'auth'
|
||||||
|
and request.endpoint != 'static'):
|
||||||
|
return redirect(url_for('auth.unconfirmed'))
|
||||||
|
if not current_user.terms_of_use_accepted:
|
||||||
|
return redirect(url_for('main.terms_of_use'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/register', methods=['GET', 'POST'])
|
@bp.route('/register', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.register', 'Register')
|
|
||||||
def register():
|
def register():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
@ -67,7 +69,6 @@ def register():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/login', methods=['GET', 'POST'])
|
@bp.route('/login', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.login', 'Login')
|
|
||||||
def login():
|
def login():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
@ -98,7 +99,6 @@ def logout():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/unconfirmed')
|
@bp.route('/unconfirmed')
|
||||||
@register_breadcrumb(bp, '.unconfirmed', 'Unconfirmed')
|
|
||||||
@login_required
|
@login_required
|
||||||
def unconfirmed():
|
def unconfirmed():
|
||||||
if current_user.confirmed:
|
if current_user.confirmed:
|
||||||
@ -141,7 +141,6 @@ def confirm(token):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/reset-password-request', methods=['GET', 'POST'])
|
@bp.route('/reset-password-request', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.reset_password_request', 'Password Reset')
|
|
||||||
def reset_password_request():
|
def reset_password_request():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
@ -171,7 +170,6 @@ def reset_password_request():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/reset-password/<token>', methods=['GET', 'POST'])
|
@bp.route('/reset-password/<token>', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.reset_password', 'Password Reset')
|
|
||||||
def reset_password(token):
|
def reset_password(token):
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
from flask import redirect, url_for
|
from flask import redirect, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('')
|
@bp.route('')
|
||||||
@register_breadcrumb(bp, '.', '<i class="material-icons left">new_label</i>My Contributions')
|
|
||||||
def contributions():
|
def contributions():
|
||||||
return redirect(url_for('main.dashboard', _anchor='contributions'))
|
return redirect(url_for('main.dashboard', _anchor='contributions'))
|
||||||
|
@ -4,7 +4,7 @@ from threading import Thread
|
|||||||
from app import db
|
from app import db
|
||||||
from app.decorators import content_negotiation, permission_required
|
from app.decorators import content_negotiation, permission_required
|
||||||
from app.models import SpaCyNLPPipelineModel
|
from app.models import SpaCyNLPPipelineModel
|
||||||
from .. import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE'])
|
@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE'])
|
||||||
@ -17,7 +17,7 @@ def delete_spacy_model(spacy_nlp_pipeline_model_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
||||||
if not (snpm.user == current_user or current_user.is_administrator()):
|
if not (snpm.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
thread = Thread(
|
thread = Thread(
|
||||||
target=_delete_spacy_model,
|
target=_delete_spacy_model,
|
||||||
@ -39,7 +39,7 @@ def update_spacy_nlp_pipeline_model_is_public(spacy_nlp_pipeline_model_id):
|
|||||||
if not isinstance(is_public, bool):
|
if not isinstance(is_public, bool):
|
||||||
abort(400)
|
abort(400)
|
||||||
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
||||||
if not (snpm.user == current_user or current_user.is_administrator()):
|
if not (snpm.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
snpm.is_public = is_public
|
snpm.is_public = is_public
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from flask import abort, flash, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import SpaCyNLPPipelineModel
|
from app.models import SpaCyNLPPipelineModel
|
||||||
@ -8,13 +7,9 @@ from .forms import (
|
|||||||
CreateSpaCyNLPPipelineModelForm,
|
CreateSpaCyNLPPipelineModelForm,
|
||||||
UpdateSpaCyNLPPipelineModelForm
|
UpdateSpaCyNLPPipelineModelForm
|
||||||
)
|
)
|
||||||
from .utils import (
|
|
||||||
spacy_nlp_pipeline_model_dlc as spacy_nlp_pipeline_model_dlc
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/spacy-nlp-pipeline-models')
|
@bp.route('/spacy-nlp-pipeline-models')
|
||||||
@register_breadcrumb(bp, '.spacy_nlp_pipeline_models', 'SpaCy NLP Pipeline Models')
|
|
||||||
def spacy_nlp_pipeline_models():
|
def spacy_nlp_pipeline_models():
|
||||||
return render_template(
|
return render_template(
|
||||||
'contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2',
|
'contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2',
|
||||||
@ -23,7 +18,6 @@ def spacy_nlp_pipeline_models():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/spacy-nlp-pipeline-models/create', methods=['GET', 'POST'])
|
@bp.route('/spacy-nlp-pipeline-models/create', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.spacy_nlp_pipeline_models.create', 'Create')
|
|
||||||
def create_spacy_nlp_pipeline_model():
|
def create_spacy_nlp_pipeline_model():
|
||||||
form = CreateSpaCyNLPPipelineModelForm()
|
form = CreateSpaCyNLPPipelineModelForm()
|
||||||
if form.is_submitted():
|
if form.is_submitted():
|
||||||
@ -57,10 +51,9 @@ def create_spacy_nlp_pipeline_model():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST'])
|
@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.spacy_nlp_pipeline_models.entity', '', dynamic_list_constructor=spacy_nlp_pipeline_model_dlc)
|
|
||||||
def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
|
def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
|
||||||
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
|
||||||
if not (snpm.user == current_user or current_user.is_administrator()):
|
if not (snpm.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
form = UpdateSpaCyNLPPipelineModelForm(data=snpm.to_json_serializeable())
|
form = UpdateSpaCyNLPPipelineModelForm(data=snpm.to_json_serializeable())
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
from flask import request, url_for
|
|
||||||
from app.models import SpaCyNLPPipelineModel
|
|
||||||
|
|
||||||
|
|
||||||
def spacy_nlp_pipeline_model_dlc():
|
|
||||||
snpm_id = request.view_args['spacy_nlp_pipeline_model_id']
|
|
||||||
snpm = SpaCyNLPPipelineModel.query.get_or_404(snpm_id)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'text': f'{snpm.title} {snpm.version}',
|
|
||||||
'url': url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=snpm_id)
|
|
||||||
}
|
|
||||||
]
|
|
@ -17,7 +17,7 @@ def delete_tesseract_model(tesseract_ocr_pipeline_model_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
||||||
if not (topm.user == current_user or current_user.is_administrator()):
|
if not (topm.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
thread = Thread(
|
thread = Thread(
|
||||||
target=_delete_tesseract_ocr_pipeline_model,
|
target=_delete_tesseract_ocr_pipeline_model,
|
||||||
@ -39,7 +39,7 @@ def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_i
|
|||||||
if not isinstance(is_public, bool):
|
if not isinstance(is_public, bool):
|
||||||
abort(400)
|
abort(400)
|
||||||
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
||||||
if not (topm.user == current_user or current_user.is_administrator()):
|
if not (topm.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
topm.is_public = is_public
|
topm.is_public = is_public
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from flask import abort, flash, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import TesseractOCRPipelineModel
|
from app.models import TesseractOCRPipelineModel
|
||||||
@ -8,13 +7,9 @@ from .forms import (
|
|||||||
CreateTesseractOCRPipelineModelForm,
|
CreateTesseractOCRPipelineModelForm,
|
||||||
UpdateTesseractOCRPipelineModelForm
|
UpdateTesseractOCRPipelineModelForm
|
||||||
)
|
)
|
||||||
from .utils import (
|
|
||||||
tesseract_ocr_pipeline_model_dlc as tesseract_ocr_pipeline_model_dlc
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/tesseract-ocr-pipeline-models')
|
@bp.route('/tesseract-ocr-pipeline-models')
|
||||||
@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models', 'Tesseract OCR Pipeline Models')
|
|
||||||
def tesseract_ocr_pipeline_models():
|
def tesseract_ocr_pipeline_models():
|
||||||
return render_template(
|
return render_template(
|
||||||
'contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2',
|
'contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2',
|
||||||
@ -23,7 +18,6 @@ def tesseract_ocr_pipeline_models():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/tesseract-ocr-pipeline-models/create', methods=['GET', 'POST'])
|
@bp.route('/tesseract-ocr-pipeline-models/create', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models.create', 'Create')
|
|
||||||
def create_tesseract_ocr_pipeline_model():
|
def create_tesseract_ocr_pipeline_model():
|
||||||
form = CreateTesseractOCRPipelineModelForm()
|
form = CreateTesseractOCRPipelineModelForm()
|
||||||
if form.is_submitted():
|
if form.is_submitted():
|
||||||
@ -56,10 +50,9 @@ def create_tesseract_ocr_pipeline_model():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST'])
|
@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models.entity', '', dynamic_list_constructor=tesseract_ocr_pipeline_model_dlc)
|
|
||||||
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
|
def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
|
||||||
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
|
||||||
if not (topm.user == current_user or current_user.is_administrator()):
|
if not (topm.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
form = UpdateTesseractOCRPipelineModelForm(data=topm.to_json_serializeable())
|
form = UpdateTesseractOCRPipelineModelForm(data=topm.to_json_serializeable())
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
from flask import request, url_for
|
|
||||||
from app.models import TesseractOCRPipelineModel
|
|
||||||
|
|
||||||
|
|
||||||
def tesseract_ocr_pipeline_model_dlc():
|
|
||||||
topm_id = request.view_args['tesseract_ocr_pipeline_model_id']
|
|
||||||
topm = TesseractOCRPipelineModel.query.get_or_404(topm_id)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'text': f'{topm.title} {topm.version}',
|
|
||||||
'url': url_for('.tesseract_ocr_pipeline_model', tesseract_ocr_pipeline_model_id=topm_id)
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,11 +1,11 @@
|
|||||||
from flask import current_app
|
|
||||||
from app import db
|
|
||||||
from app.models import User, Corpus, CorpusFile
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from flask import current_app
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
|
from app import db
|
||||||
|
from app.models import User, Corpus, CorpusFile
|
||||||
|
|
||||||
|
|
||||||
class SandpaperConverter:
|
class SandpaperConverter:
|
||||||
|
@ -99,7 +99,7 @@ class CQiNamespace(Namespace):
|
|||||||
return {'code': 404, 'msg': 'Not Found'}
|
return {'code': 404, 'msg': 'Not Found'}
|
||||||
if not (db_corpus.user == current_user
|
if not (db_corpus.user == current_user
|
||||||
or current_user.is_following_corpus(db_corpus)
|
or current_user.is_following_corpus(db_corpus)
|
||||||
or current_user.is_administrator()):
|
or current_user.is_administrator):
|
||||||
return {'code': 403, 'msg': 'Forbidden'}
|
return {'code': 403, 'msg': 'Forbidden'}
|
||||||
if db_corpus.status not in [
|
if db_corpus.status not in [
|
||||||
CorpusStatus.BUILT,
|
CorpusStatus.BUILT,
|
||||||
|
@ -10,7 +10,7 @@ def corpus_follower_permission_required(*permissions):
|
|||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
corpus_id = kwargs.get('corpus_id')
|
corpus_id = kwargs.get('corpus_id')
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
if not (corpus.user == current_user or current_user.is_administrator):
|
||||||
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||||
if cfa is None:
|
if cfa is None:
|
||||||
abort(403)
|
abort(403)
|
||||||
@ -26,7 +26,7 @@ def corpus_owner_or_admin_required(f):
|
|||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
corpus_id = kwargs.get('corpus_id')
|
corpus_id = kwargs.get('corpus_id')
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
if not (corpus.user == current_user or current_user.is_administrator()):
|
if not (corpus.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
@ -15,7 +15,7 @@ def get_corpus(corpus_hashid):
|
|||||||
if not (
|
if not (
|
||||||
corpus.is_public
|
corpus.is_public
|
||||||
or corpus.user == current_user
|
or corpus.user == current_user
|
||||||
or current_user.is_administrator()
|
or current_user.is_administrator
|
||||||
):
|
):
|
||||||
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
||||||
return {
|
return {
|
||||||
@ -38,7 +38,7 @@ def subscribe_corpus(corpus_hashid):
|
|||||||
if not (
|
if not (
|
||||||
corpus.is_public
|
corpus.is_public
|
||||||
or corpus.user == current_user
|
or corpus.user == current_user
|
||||||
or current_user.is_administrator()
|
or current_user.is_administrator
|
||||||
):
|
):
|
||||||
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
||||||
join_room(f'/corpora/{corpus.hashid}')
|
join_room(f'/corpora/{corpus.hashid}')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from flask import abort, current_app
|
from flask import current_app
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from app import db
|
|
||||||
from app.decorators import content_negotiation
|
from app.decorators import content_negotiation
|
||||||
|
from app import db
|
||||||
from app.models import CorpusFile
|
from app.models import CorpusFile
|
||||||
from ..decorators import corpus_follower_permission_required
|
from ..decorators import corpus_follower_permission_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
@ -6,24 +6,19 @@ from flask import (
|
|||||||
send_from_directory,
|
send_from_directory,
|
||||||
url_for
|
url_for
|
||||||
)
|
)
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Corpus, CorpusFile, CorpusStatus
|
from app.models import Corpus, CorpusFile, CorpusStatus
|
||||||
from ..decorators import corpus_follower_permission_required
|
from ..decorators import corpus_follower_permission_required
|
||||||
from ..utils import corpus_endpoint_arguments_constructor as corpus_eac
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from .forms import CreateCorpusFileForm, UpdateCorpusFileForm
|
from .forms import CreateCorpusFileForm, UpdateCorpusFileForm
|
||||||
from .utils import corpus_file_dynamic_list_constructor as corpus_file_dlc
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/files')
|
@bp.route('/<hashid:corpus_id>/files')
|
||||||
@register_breadcrumb(bp, '.entity.files', 'Files', endpoint_arguments_constructor=corpus_eac)
|
|
||||||
def corpus_files(corpus_id):
|
def corpus_files(corpus_id):
|
||||||
return redirect(url_for('.corpus', _anchor='files', corpus_id=corpus_id))
|
return redirect(url_for('.corpus', _anchor='files', corpus_id=corpus_id))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/files/create', methods=['GET', 'POST'])
|
@bp.route('/<hashid:corpus_id>/files/create', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.entity.files.create', 'Create', endpoint_arguments_constructor=corpus_eac)
|
|
||||||
@corpus_follower_permission_required('MANAGE_FILES')
|
@corpus_follower_permission_required('MANAGE_FILES')
|
||||||
def create_corpus_file(corpus_id):
|
def create_corpus_file(corpus_id):
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
@ -65,7 +60,6 @@ def create_corpus_file(corpus_id):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST'])
|
@bp.route('/<hashid:corpus_id>/files/<hashid:corpus_file_id>', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.entity.files.entity', '', dynamic_list_constructor=corpus_file_dlc)
|
|
||||||
@corpus_follower_permission_required('MANAGE_FILES')
|
@corpus_follower_permission_required('MANAGE_FILES')
|
||||||
def corpus_file(corpus_id, corpus_file_id):
|
def corpus_file(corpus_id, corpus_file_id):
|
||||||
corpus_file = CorpusFile.query.filter_by(corpus_id=corpus_id, id=corpus_file_id).first_or_404()
|
corpus_file = CorpusFile.query.filter_by(corpus_id=corpus_id, id=corpus_file_id).first_or_404()
|
||||||
@ -94,6 +88,6 @@ def download_corpus_file(corpus_id, corpus_file_id):
|
|||||||
corpus_file.path.parent,
|
corpus_file.path.parent,
|
||||||
corpus_file.path.name,
|
corpus_file.path.name,
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
attachment_filename=corpus_file.filename,
|
download_name=corpus_file.filename,
|
||||||
mimetype=corpus_file.mimetype
|
mimetype=corpus_file.mimetype
|
||||||
)
|
)
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
from flask import request, url_for
|
|
||||||
from app.models import CorpusFile
|
|
||||||
from ..utils import corpus_endpoint_arguments_constructor as corpus_eac
|
|
||||||
|
|
||||||
|
|
||||||
def corpus_file_dynamic_list_constructor():
|
|
||||||
corpus_id = request.view_args['corpus_id']
|
|
||||||
corpus_file_id = request.view_args['corpus_file_id']
|
|
||||||
corpus_file = CorpusFile.query.filter_by(corpus_id=corpus_id, id=corpus_file_id).first_or_404()
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'text': f'{corpus_file.author}: {corpus_file.title} ({corpus_file.publishing_year})',
|
|
||||||
'url': url_for('.corpus_file', corpus_id=corpus_id, corpus_file_id=corpus_file_id)
|
|
||||||
}
|
|
||||||
]
|
|
@ -58,7 +58,7 @@ def delete_corpus_follower(corpus_id, follower_id):
|
|||||||
current_user.id == follower_id
|
current_user.id == follower_id
|
||||||
or current_user == cfa.corpus.user
|
or current_user == cfa.corpus.user
|
||||||
or CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first().role.has_permission('MANAGE_FOLLOWERS')
|
or CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first().role.has_permission('MANAGE_FOLLOWERS')
|
||||||
or current_user.is_administrator()):
|
or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
if current_user.id == follower_id:
|
if current_user.id == follower_id:
|
||||||
flash(f'You are no longer following "{cfa.corpus.title}"', 'corpus')
|
flash(f'You are no longer following "{cfa.corpus.title}"', 'corpus')
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
from flask import abort, flash, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import (
|
from app.models import (
|
||||||
@ -11,20 +10,14 @@ from app.models import (
|
|||||||
from . import bp
|
from . import bp
|
||||||
from .decorators import corpus_follower_permission_required
|
from .decorators import corpus_follower_permission_required
|
||||||
from .forms import CreateCorpusForm
|
from .forms import CreateCorpusForm
|
||||||
from .utils import (
|
|
||||||
corpus_endpoint_arguments_constructor as corpus_eac,
|
|
||||||
corpus_dynamic_list_constructor as corpus_dlc
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('')
|
@bp.route('')
|
||||||
@register_breadcrumb(bp, '.', '<i class="nopaque-icons left">I</i>My Corpora')
|
|
||||||
def corpora():
|
def corpora():
|
||||||
return redirect(url_for('main.dashboard', _anchor='corpora'))
|
return redirect(url_for('main.dashboard', _anchor='corpora'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/create', methods=['GET', 'POST'])
|
@bp.route('/create', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.create', 'Create')
|
|
||||||
def create_corpus():
|
def create_corpus():
|
||||||
form = CreateCorpusForm()
|
form = CreateCorpusForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@ -47,7 +40,6 @@ def create_corpus():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>')
|
@bp.route('/<hashid:corpus_id>')
|
||||||
@register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=corpus_dlc)
|
|
||||||
def corpus(corpus_id):
|
def corpus(corpus_id):
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
cfrs = CorpusFollowerRole.query.all()
|
cfrs = CorpusFollowerRole.query.all()
|
||||||
@ -55,13 +47,13 @@ def corpus(corpus_id):
|
|||||||
users = User.query.filter(User.is_public == True, User.id != current_user.id, User.id != corpus.user.id, User.role_id < 4).all()
|
users = User.query.filter(User.is_public == True, User.id != current_user.id, User.id != corpus.user.id, User.role_id < 4).all()
|
||||||
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||||
if cfa is None:
|
if cfa is None:
|
||||||
if corpus.user == current_user or current_user.is_administrator():
|
if corpus.user == current_user or current_user.is_administrator:
|
||||||
cfr = CorpusFollowerRole.query.filter_by(name='Administrator').first()
|
cfr = CorpusFollowerRole.query.filter_by(name='Administrator').first()
|
||||||
else:
|
else:
|
||||||
cfr = CorpusFollowerRole.query.filter_by(name='Anonymous').first()
|
cfr = CorpusFollowerRole.query.filter_by(name='Anonymous').first()
|
||||||
else:
|
else:
|
||||||
cfr = cfa.role
|
cfr = cfa.role
|
||||||
if corpus.user == current_user or current_user.is_administrator():
|
if corpus.user == current_user or current_user.is_administrator:
|
||||||
return render_template(
|
return render_template(
|
||||||
'corpora/corpus.html.j2',
|
'corpora/corpus.html.j2',
|
||||||
title=corpus.title,
|
title=corpus.title,
|
||||||
@ -87,7 +79,6 @@ def corpus(corpus_id):
|
|||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/analysis')
|
@bp.route('/<hashid:corpus_id>/analysis')
|
||||||
@corpus_follower_permission_required('VIEW')
|
@corpus_follower_permission_required('VIEW')
|
||||||
@register_breadcrumb(bp, '.entity.analysis', 'Analysis', endpoint_arguments_constructor=corpus_eac)
|
|
||||||
def analysis(corpus_id):
|
def analysis(corpus_id):
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
corpus = Corpus.query.get_or_404(corpus_id)
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -108,13 +99,11 @@ def follow_corpus(corpus_id, token):
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/import', methods=['GET', 'POST'])
|
@bp.route('/import', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.import', 'Import')
|
|
||||||
def import_corpus():
|
def import_corpus():
|
||||||
abort(503)
|
abort(503)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:corpus_id>/export')
|
@bp.route('/<hashid:corpus_id>/export')
|
||||||
@corpus_follower_permission_required('VIEW')
|
@corpus_follower_permission_required('VIEW')
|
||||||
@register_breadcrumb(bp, '.entity.export', 'Export', endpoint_arguments_constructor=corpus_eac)
|
|
||||||
def export_corpus(corpus_id):
|
def export_corpus(corpus_id):
|
||||||
abort(503)
|
abort(503)
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
from flask import request, url_for
|
|
||||||
from app.models import Corpus
|
|
||||||
|
|
||||||
|
|
||||||
def corpus_endpoint_arguments_constructor():
|
|
||||||
return {'corpus_id': request.view_args['corpus_id']}
|
|
||||||
|
|
||||||
|
|
||||||
def corpus_dynamic_list_constructor():
|
|
||||||
corpus_id = request.view_args['corpus_id']
|
|
||||||
corpus = Corpus.query.get_or_404(corpus_id)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'text': f'<i class="material-icons left">book</i>{corpus.title}',
|
|
||||||
'url': url_for('.corpus', corpus_id=corpus_id)
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,7 +1,6 @@
|
|||||||
from flask import abort, current_app, request
|
from flask import abort, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from threading import Thread
|
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
from werkzeug.exceptions import NotAcceptable
|
from werkzeug.exceptions import NotAcceptable
|
||||||
from app.models import Permission
|
from app.models import Permission
|
||||||
@ -24,22 +23,21 @@ def admin_required(f):
|
|||||||
|
|
||||||
def socketio_login_required(f):
|
def socketio_login_required(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
else:
|
return {'code': 401, 'body': 'Unauthorized'}
|
||||||
return {'code': 401, 'msg': 'Unauthorized'}
|
return wrapper
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
|
|
||||||
def socketio_permission_required(permission):
|
def socketio_permission_required(permission):
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
if not current_user.can(permission):
|
if not current_user.can(permission):
|
||||||
return {'code': 403, 'msg': 'Forbidden'}
|
return {'code': 403, 'body': 'Forbidden'}
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@ -47,24 +45,6 @@ def socketio_admin_required(f):
|
|||||||
return socketio_permission_required(Permission.ADMINISTRATE)(f)
|
return socketio_permission_required(Permission.ADMINISTRATE)(f)
|
||||||
|
|
||||||
|
|
||||||
def background(f):
|
|
||||||
'''
|
|
||||||
' This decorator executes a function in a Thread.
|
|
||||||
' Decorated functions need to be executed within a code block where an
|
|
||||||
' app context exists.
|
|
||||||
'
|
|
||||||
' NOTE: An app object is passed as a keyword argument to the decorated
|
|
||||||
' function.
|
|
||||||
'''
|
|
||||||
@wraps(f)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
kwargs['app'] = current_app._get_current_object()
|
|
||||||
thread = Thread(target=f, args=args, kwargs=kwargs)
|
|
||||||
thread.start()
|
|
||||||
return thread
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def content_negotiation(
|
def content_negotiation(
|
||||||
produces: Union[str, List[str], None] = None,
|
produces: Union[str, List[str], None] = None,
|
||||||
consumes: Union[str, List[str], None] = None
|
consumes: Union[str, List[str], None] = None
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
from .container_column import ContainerColumn
|
|
||||||
from .int_enum_column import IntEnumColumn
|
|
@ -1,21 +0,0 @@
|
|||||||
import json
|
|
||||||
from app import db
|
|
||||||
|
|
||||||
|
|
||||||
class ContainerColumn(db.TypeDecorator):
|
|
||||||
impl = db.String
|
|
||||||
|
|
||||||
def __init__(self, container_type, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.container_type = container_type
|
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
|
||||||
if isinstance(value, self.container_type):
|
|
||||||
return json.dumps(value)
|
|
||||||
elif isinstance(value, str) and isinstance(json.loads(value), self.container_type):
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
return TypeError()
|
|
||||||
|
|
||||||
def process_result_value(self, value, dialect):
|
|
||||||
return json.loads(value)
|
|
2
app/extensions/sqlalchemy_extras/__init__.py
Normal file
2
app/extensions/sqlalchemy_extras/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .types import ContainerColumn
|
||||||
|
from .types import IntEnumColumn
|
@ -1,6 +1,26 @@
|
|||||||
|
import json
|
||||||
from app import db
|
from app import db
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerColumn(db.TypeDecorator):
|
||||||
|
impl = db.String
|
||||||
|
|
||||||
|
def __init__(self, container_type, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.container_type = container_type
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
if isinstance(value, self.container_type):
|
||||||
|
return json.dumps(value)
|
||||||
|
elif isinstance(value, str) and isinstance(json.loads(value), self.container_type):
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return TypeError()
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
return json.loads(value)
|
||||||
|
|
||||||
|
|
||||||
class IntEnumColumn(db.TypeDecorator):
|
class IntEnumColumn(db.TypeDecorator):
|
||||||
impl = db.Integer
|
impl = db.Integer
|
||||||
|
|
138
app/jobs/events.py
Normal file
138
app/jobs/events.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
from flask import current_app
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import Namespace
|
||||||
|
from app import db, hashids, socketio
|
||||||
|
from app.extensions.flask_socketio import admin_required, login_required
|
||||||
|
from app.models import Job, JobStatus
|
||||||
|
|
||||||
|
|
||||||
|
class JobsNamespace(Namespace):
|
||||||
|
@login_required
|
||||||
|
def on_delete(self, job_hashid: str):
|
||||||
|
# Decode the job hashid
|
||||||
|
job_id = hashids.decode(job_hashid)
|
||||||
|
|
||||||
|
# Validate job_id
|
||||||
|
if not isinstance(job_id, int):
|
||||||
|
return {
|
||||||
|
'code': 400,
|
||||||
|
'body': 'job_id is invalid'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load job from database
|
||||||
|
job = Job.query.get(job_id)
|
||||||
|
if job is None:
|
||||||
|
return {
|
||||||
|
'code': 404,
|
||||||
|
'body': 'Job not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the current user is allowed to delete the job
|
||||||
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
|
return {
|
||||||
|
'code': 403,
|
||||||
|
'body': 'Forbidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: This should be a method in the Job model
|
||||||
|
def _delete_job(app, job_id):
|
||||||
|
with app.app_context():
|
||||||
|
job = Job.query.get(job_id)
|
||||||
|
job.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Delete the job in a background task
|
||||||
|
socketio.start_background_task(
|
||||||
|
target=_delete_job,
|
||||||
|
app=current_app._get_current_object(),
|
||||||
|
job_id=job_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'code': 202,
|
||||||
|
'body': f'Job "{job.title}" marked for deletion'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@admin_required
|
||||||
|
def on_get_log(self, job_hashid: str):
|
||||||
|
# Decode the job hashid
|
||||||
|
job_id = hashids.decode(job_hashid)
|
||||||
|
|
||||||
|
# Validate job_id
|
||||||
|
if not isinstance(job_id, int):
|
||||||
|
return {
|
||||||
|
'code': 400,
|
||||||
|
'body': 'job_id is invalid'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load job from database
|
||||||
|
job = Job.query.get(job_id)
|
||||||
|
if job is None:
|
||||||
|
return {
|
||||||
|
'code': 404,
|
||||||
|
'body': 'Job not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the job is already processed
|
||||||
|
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||||
|
return {
|
||||||
|
'code': 409,
|
||||||
|
'body': 'Job is not done processing'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the log file
|
||||||
|
with open(job.path / 'pipeline_data' / 'logs' / 'pyflow_log.txt') as log_file:
|
||||||
|
job_log = log_file.read()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'code': 200,
|
||||||
|
'body': job_log
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def on_restart(self, job_hashid: str):
|
||||||
|
# Decode the job hashid
|
||||||
|
job_id = hashids.decode(job_hashid)
|
||||||
|
|
||||||
|
# Validate job_id
|
||||||
|
if not isinstance(job_id, int):
|
||||||
|
return {
|
||||||
|
'code': 400,
|
||||||
|
'body': 'job_id is invalid'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load job from database
|
||||||
|
job = Job.query.get(job_id)
|
||||||
|
if job is None:
|
||||||
|
return {
|
||||||
|
'code': 404,
|
||||||
|
'body': 'Job not found'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the current user is allowed to restart the job
|
||||||
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
|
return {
|
||||||
|
'code': 403,
|
||||||
|
'body': 'Forbidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: This should be a method in the Job model
|
||||||
|
def _restart_job(app, job_id):
|
||||||
|
with app.app_context():
|
||||||
|
job = Job.query.get(job_id)
|
||||||
|
job.restart()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Restart the job in a background task
|
||||||
|
socketio.start_background_task(
|
||||||
|
target=_restart_job,
|
||||||
|
app=current_app._get_current_object(),
|
||||||
|
job_id=job_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'code': 202,
|
||||||
|
'body': f'Job "{job.title}" restarted'
|
||||||
|
}
|
@ -17,7 +17,7 @@ def delete_job(job_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
job = Job.query.get_or_404(job_id)
|
job = Job.query.get_or_404(job_id)
|
||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
thread = Thread(
|
thread = Thread(
|
||||||
target=_delete_job,
|
target=_delete_job,
|
||||||
@ -56,7 +56,7 @@ def restart_job(job_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
job = Job.query.get_or_404(job_id)
|
job = Job.query.get_or_404(job_id)
|
||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
if job.status == JobStatus.FAILED:
|
if job.status == JobStatus.FAILED:
|
||||||
response = {'errors': {'message': 'Job status is not "failed"'}}
|
response = {'errors': {'message': 'Job status is not "failed"'}}
|
||||||
|
@ -5,24 +5,20 @@ from flask import (
|
|||||||
send_from_directory,
|
send_from_directory,
|
||||||
url_for
|
url_for
|
||||||
)
|
)
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app.models import Job, JobInput, JobResult
|
from app.models import Job, JobInput, JobResult
|
||||||
from . import bp
|
from . import bp
|
||||||
from .utils import job_dynamic_list_constructor as job_dlc
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('')
|
@bp.route('')
|
||||||
@register_breadcrumb(bp, '.', '<i class="nopaque-icons left">J</i>My Jobs')
|
|
||||||
def corpora():
|
def corpora():
|
||||||
return redirect(url_for('main.dashboard', _anchor='jobs'))
|
return redirect(url_for('main.dashboard', _anchor='jobs'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<hashid:job_id>')
|
@bp.route('/<hashid:job_id>')
|
||||||
@register_breadcrumb(bp, '.entity', '', dynamic_list_constructor=job_dlc)
|
|
||||||
def job(job_id):
|
def job(job_id):
|
||||||
job = Job.query.get_or_404(job_id)
|
job = Job.query.get_or_404(job_id)
|
||||||
if not (job.user == current_user or current_user.is_administrator()):
|
if not (job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return render_template(
|
return render_template(
|
||||||
'jobs/job.html.j2',
|
'jobs/job.html.j2',
|
||||||
@ -34,13 +30,13 @@ def job(job_id):
|
|||||||
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
|
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
|
||||||
def download_job_input(job_id, job_input_id):
|
def download_job_input(job_id, job_input_id):
|
||||||
job_input = JobInput.query.filter_by(job_id=job_id, id=job_input_id).first_or_404()
|
job_input = JobInput.query.filter_by(job_id=job_id, id=job_input_id).first_or_404()
|
||||||
if not (job_input.job.user == current_user or current_user.is_administrator()):
|
if not (job_input.job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
job_input.path.parent,
|
job_input.path.parent,
|
||||||
job_input.path.name,
|
job_input.path.name,
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
attachment_filename=job_input.filename,
|
download_name=job_input.filename,
|
||||||
mimetype=job_input.mimetype
|
mimetype=job_input.mimetype
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,12 +44,12 @@ def download_job_input(job_id, job_input_id):
|
|||||||
@bp.route('/<hashid:job_id>/results/<hashid:job_result_id>/download')
|
@bp.route('/<hashid:job_id>/results/<hashid:job_result_id>/download')
|
||||||
def download_job_result(job_id, job_result_id):
|
def download_job_result(job_id, job_result_id):
|
||||||
job_result = JobResult.query.filter_by(job_id=job_id, id=job_result_id).first_or_404()
|
job_result = JobResult.query.filter_by(job_id=job_id, id=job_result_id).first_or_404()
|
||||||
if not (job_result.job.user == current_user or current_user.is_administrator()):
|
if not (job_result.job.user == current_user or current_user.is_administrator):
|
||||||
abort(403)
|
abort(403)
|
||||||
return send_from_directory(
|
return send_from_directory(
|
||||||
job_result.path.parent,
|
job_result.path.parent,
|
||||||
job_result.path.name,
|
job_result.path.name,
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
attachment_filename=job_result.filename,
|
download_name=job_result.filename,
|
||||||
mimetype=job_result.mimetype
|
mimetype=job_result.mimetype
|
||||||
)
|
)
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
from flask import request, url_for
|
|
||||||
from app.models import Job
|
|
||||||
|
|
||||||
|
|
||||||
def job_dynamic_list_constructor():
|
|
||||||
job_id = request.view_args['job_id']
|
|
||||||
job = Job.query.get_or_404(job_id)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'text': f'<i class="nopaque-icons left service-icons" data-service="{job.service}"></i>{job.title}',
|
|
||||||
'url': url_for('.job', job_id=job_id)
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,14 +1,11 @@
|
|||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user, login_required, login_user
|
from flask_login import current_user, login_required, login_user
|
||||||
from app.auth.forms import LoginForm
|
from app.auth.forms import LoginForm
|
||||||
from app.models import Corpus, User
|
from app.models import Corpus, User
|
||||||
from sqlalchemy import or_
|
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/', methods=['GET', 'POST'])
|
@bp.route('/', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.', '<i class="material-icons">home</i>')
|
|
||||||
def index():
|
def index():
|
||||||
form = LoginForm()
|
form = LoginForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@ -27,7 +24,6 @@ def index():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/faq')
|
@bp.route('/faq')
|
||||||
@register_breadcrumb(bp, '.faq', 'Frequently Asked Questions')
|
|
||||||
def faq():
|
def faq():
|
||||||
return render_template(
|
return render_template(
|
||||||
'main/faq.html.j2',
|
'main/faq.html.j2',
|
||||||
@ -36,7 +32,6 @@ def faq():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/dashboard')
|
@bp.route('/dashboard')
|
||||||
@register_breadcrumb(bp, '.dashboard', '<i class="material-icons left">dashboard</i>Dashboard')
|
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -45,8 +40,15 @@ def dashboard():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/manual')
|
||||||
|
def manual():
|
||||||
|
return render_template(
|
||||||
|
'main/manual.html.j2',
|
||||||
|
title='Manual'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/news')
|
@bp.route('/news')
|
||||||
@register_breadcrumb(bp, '.news', '<i class="material-icons left">email</i>News')
|
|
||||||
def news():
|
def news():
|
||||||
return render_template(
|
return render_template(
|
||||||
'main/news.html.j2',
|
'main/news.html.j2',
|
||||||
@ -55,7 +57,6 @@ def news():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/privacy_policy')
|
@bp.route('/privacy_policy')
|
||||||
@register_breadcrumb(bp, '.privacy_policy', 'Private statement (GDPR)')
|
|
||||||
def privacy_policy():
|
def privacy_policy():
|
||||||
return render_template(
|
return render_template(
|
||||||
'main/privacy_policy.html.j2',
|
'main/privacy_policy.html.j2',
|
||||||
@ -64,7 +65,6 @@ def privacy_policy():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/terms_of_use')
|
@bp.route('/terms_of_use')
|
||||||
@register_breadcrumb(bp, '.terms_of_use', 'Terms of Use')
|
|
||||||
def terms_of_use():
|
def terms_of_use():
|
||||||
return render_template(
|
return render_template(
|
||||||
'main/terms_of_use.html.j2',
|
'main/terms_of_use.html.j2',
|
||||||
@ -73,7 +73,6 @@ def terms_of_use():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/social-area')
|
@bp.route('/social-area')
|
||||||
@register_breadcrumb(bp, '.social_area', '<i class="material-icons left">group</i>Social Area')
|
|
||||||
@login_required
|
@login_required
|
||||||
def social_area():
|
def social_area():
|
||||||
print('test')
|
print('test')
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from flask_login import AnonymousUserMixin
|
||||||
|
from app import db, login, mail, socketio
|
||||||
|
from app.email import create_message
|
||||||
from .avatar import *
|
from .avatar import *
|
||||||
from .corpus_file import *
|
from .corpus_file import *
|
||||||
from .corpus_follower_association import *
|
from .corpus_follower_association import *
|
||||||
@ -11,7 +15,142 @@ from .spacy_nlp_pipeline_model import *
|
|||||||
from .tesseract_ocr_pipeline_model import *
|
from .tesseract_ocr_pipeline_model import *
|
||||||
from .token import *
|
from .token import *
|
||||||
from .user import *
|
from .user import *
|
||||||
from app import login
|
|
||||||
|
|
||||||
|
@db.event.listens_for(Corpus, 'after_delete')
|
||||||
|
@db.event.listens_for(CorpusFile, 'after_delete')
|
||||||
|
@db.event.listens_for(Job, 'after_delete')
|
||||||
|
@db.event.listens_for(JobInput, 'after_delete')
|
||||||
|
@db.event.listens_for(JobResult, 'after_delete')
|
||||||
|
@db.event.listens_for(SpaCyNLPPipelineModel, 'after_delete')
|
||||||
|
@db.event.listens_for(TesseractOCRPipelineModel, 'after_delete')
|
||||||
|
def resource_after_delete(mapper, connection, resource):
|
||||||
|
print('[START] resource_after_delete')
|
||||||
|
jsonpatch = [
|
||||||
|
{
|
||||||
|
'op': 'remove',
|
||||||
|
'path': resource.jsonpatch_path
|
||||||
|
}
|
||||||
|
]
|
||||||
|
room = f'/users/{resource.user_hashid}'
|
||||||
|
print('[EMIT] PATCH', jsonpatch)
|
||||||
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
print('[END] resource_after_delete')
|
||||||
|
|
||||||
|
|
||||||
|
@db.event.listens_for(CorpusFollowerAssociation, 'after_delete')
|
||||||
|
def cfa_after_delete_handler(mapper, connection, cfa):
|
||||||
|
jsonpatch_path = f'/users/{cfa.corpus.user.hashid}/corpora/{cfa.corpus.hashid}/corpus_follower_associations/{cfa.hashid}'
|
||||||
|
jsonpatch = [
|
||||||
|
{
|
||||||
|
'op': 'remove',
|
||||||
|
'path': jsonpatch_path
|
||||||
|
}
|
||||||
|
]
|
||||||
|
room = f'/users/{cfa.corpus.user.hashid}'
|
||||||
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
|
@db.event.listens_for(Corpus, 'after_insert')
|
||||||
|
@db.event.listens_for(CorpusFile, 'after_insert')
|
||||||
|
@db.event.listens_for(Job, 'after_insert')
|
||||||
|
@db.event.listens_for(JobInput, 'after_insert')
|
||||||
|
@db.event.listens_for(JobResult, 'after_insert')
|
||||||
|
@db.event.listens_for(SpaCyNLPPipelineModel, 'after_insert')
|
||||||
|
@db.event.listens_for(TesseractOCRPipelineModel, 'after_insert')
|
||||||
|
def resource_after_insert_handler(mapper, connection, resource):
|
||||||
|
jsonpatch_value = resource.to_json_serializeable()
|
||||||
|
for attr in mapper.relationships:
|
||||||
|
jsonpatch_value[attr.key] = {}
|
||||||
|
jsonpatch = [
|
||||||
|
{
|
||||||
|
'op': 'add',
|
||||||
|
'path': resource.jsonpatch_path,
|
||||||
|
'value': jsonpatch_value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
room = f'/users/{resource.user_hashid}'
|
||||||
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
|
@db.event.listens_for(CorpusFollowerAssociation, 'after_insert')
|
||||||
|
def cfa_after_insert_handler(mapper, connection, cfa):
|
||||||
|
jsonpatch_value = cfa.to_json_serializeable()
|
||||||
|
jsonpatch_path = f'/users/{cfa.corpus.user.hashid}/corpora/{cfa.corpus.hashid}/corpus_follower_associations/{cfa.hashid}'
|
||||||
|
jsonpatch = [
|
||||||
|
{
|
||||||
|
'op': 'add',
|
||||||
|
'path': jsonpatch_path,
|
||||||
|
'value': jsonpatch_value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
room = f'/users/{cfa.corpus.user.hashid}'
|
||||||
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
|
@db.event.listens_for(Corpus, 'after_update')
|
||||||
|
@db.event.listens_for(CorpusFile, 'after_update')
|
||||||
|
@db.event.listens_for(Job, 'after_update')
|
||||||
|
@db.event.listens_for(JobInput, 'after_update')
|
||||||
|
@db.event.listens_for(JobResult, 'after_update')
|
||||||
|
@db.event.listens_for(SpaCyNLPPipelineModel, 'after_update')
|
||||||
|
@db.event.listens_for(TesseractOCRPipelineModel, 'after_update')
|
||||||
|
def resource_after_update_handler(mapper, connection, resource):
|
||||||
|
jsonpatch = []
|
||||||
|
for attr in db.inspect(resource).attrs:
|
||||||
|
if attr.key in mapper.relationships:
|
||||||
|
continue
|
||||||
|
if not attr.load_history().has_changes():
|
||||||
|
continue
|
||||||
|
jsonpatch_path = f'{resource.jsonpatch_path}/{attr.key}'
|
||||||
|
if isinstance(attr.value, datetime):
|
||||||
|
jsonpatch_value = f'{attr.value.isoformat()}Z'
|
||||||
|
elif isinstance(attr.value, Enum):
|
||||||
|
jsonpatch_value = attr.value.name
|
||||||
|
else:
|
||||||
|
jsonpatch_value = attr.value
|
||||||
|
jsonpatch.append(
|
||||||
|
{
|
||||||
|
'op': 'replace',
|
||||||
|
'path': jsonpatch_path,
|
||||||
|
'value': jsonpatch_value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if jsonpatch:
|
||||||
|
room = f'/users/{resource.user_hashid}'
|
||||||
|
socketio.emit('PATCH', jsonpatch, room=room)
|
||||||
|
|
||||||
|
|
||||||
|
@db.event.listens_for(Job, 'after_update')
|
||||||
|
def job_after_update_handler(mapper, connection, job):
|
||||||
|
for attr in db.inspect(job).attrs:
|
||||||
|
if attr.key != 'status':
|
||||||
|
continue
|
||||||
|
if not attr.load_history().has_changes():
|
||||||
|
return
|
||||||
|
if job.user.setting_job_status_mail_notification_level == UserSettingJobStatusMailNotificationLevel.NONE:
|
||||||
|
return
|
||||||
|
if job.user.setting_job_status_mail_notification_level == UserSettingJobStatusMailNotificationLevel.END:
|
||||||
|
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||||
|
return
|
||||||
|
msg = create_message(
|
||||||
|
job.user.email,
|
||||||
|
f'Status update for your Job "{job.title}"',
|
||||||
|
'tasks/email/notification',
|
||||||
|
job=job
|
||||||
|
)
|
||||||
|
mail.send(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class AnonymousUser(AnonymousUserMixin):
|
||||||
|
def can(self, permissions):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_administrator(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
login.anonymous_user = AnonymousUser
|
||||||
|
|
||||||
|
|
||||||
@login.user_loader
|
@login.user_loader
|
||||||
|
@ -9,7 +9,7 @@ import shutil
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from app import db
|
from app import db
|
||||||
from app.converters.vrt import normalize_vrt_file
|
from app.converters.vrt import normalize_vrt_file
|
||||||
from app.ext.flask_sqlalchemy import IntEnumColumn
|
from app.extensions.sqlalchemy_extras import IntEnumColumn
|
||||||
from .corpus_follower_association import CorpusFollowerAssociation
|
from .corpus_follower_association import CorpusFollowerAssociation
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from typing import Union
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
from app import db
|
from app import db
|
||||||
from app.ext.flask_sqlalchemy import ContainerColumn, IntEnumColumn
|
from app.extensions.sqlalchemy_extras import ContainerColumn, IntEnumColumn
|
||||||
|
|
||||||
|
|
||||||
class JobStatus(IntEnum):
|
class JobStatus(IntEnum):
|
||||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
from app import db
|
from app import db
|
||||||
from app.ext.flask_sqlalchemy import ContainerColumn
|
from app.extensions.sqlalchemy_extras import ContainerColumn
|
||||||
from .file_mixin import FileMixin
|
from .file_mixin import FileMixin
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
from app import db
|
from app import db
|
||||||
from app.ext.flask_sqlalchemy import ContainerColumn
|
from app.extensions.sqlalchemy_extras import ContainerColumn
|
||||||
from .file_mixin import FileMixin
|
from .file_mixin import FileMixin
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import re
|
|||||||
import secrets
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
from app import db, hashids
|
from app import db, hashids
|
||||||
from app.ext.flask_sqlalchemy import IntEnumColumn
|
from app.extensions.sqlalchemy_extras import IntEnumColumn
|
||||||
from .corpus import Corpus
|
from .corpus import Corpus
|
||||||
from .corpus_follower_association import CorpusFollowerAssociation
|
from .corpus_follower_association import CorpusFollowerAssociation
|
||||||
from .corpus_follower_role import CorpusFollowerRole
|
from .corpus_follower_role import CorpusFollowerRole
|
||||||
@ -132,6 +132,10 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<User {self.username}>'
|
return f'<User {self.username}>'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_administrator(self):
|
||||||
|
return self.can(Permission.ADMINISTRATE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def jsonpatch_path(self):
|
def jsonpatch_path(self):
|
||||||
return f'/users/{self.hashid}'
|
return f'/users/{self.hashid}'
|
||||||
@ -142,7 +146,8 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
|
|
||||||
@password.setter
|
@password.setter
|
||||||
def password(self, password):
|
def password(self, password):
|
||||||
self.password_hash = generate_password_hash(password)
|
#pbkdf2:sha256
|
||||||
|
self.password_hash = generate_password_hash(password, method='pbkdf2')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
@ -294,9 +299,6 @@ class User(HashidMixin, UserMixin, db.Model):
|
|||||||
algorithm='HS256'
|
algorithm='HS256'
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_administrator(self):
|
|
||||||
return self.can(Permission.ADMINISTRATE)
|
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
self.last_seen = datetime.utcnow()
|
self.last_seen = datetime.utcnow()
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
from flask import abort, current_app, flash, Markup, redirect, render_template, request, url_for
|
from flask import abort, current_app, flash, redirect, render_template, request, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
import requests
|
import requests
|
||||||
from app import db, hashids
|
from app import db, hashids
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Job,
|
Job,
|
||||||
JobInput,
|
JobInput,
|
||||||
JobResult,
|
|
||||||
JobStatus,
|
JobStatus,
|
||||||
TesseractOCRPipelineModel,
|
TesseractOCRPipelineModel,
|
||||||
SpaCyNLPPipelineModel
|
SpaCyNLPPipelineModel
|
||||||
@ -21,13 +19,11 @@ from .forms import (
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/services')
|
@bp.route('/services')
|
||||||
@register_breadcrumb(bp, '.', 'Services')
|
|
||||||
def services():
|
def services():
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/file-setup-pipeline', methods=['GET', 'POST'])
|
@bp.route('/file-setup-pipeline', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.file_setup_pipeline', '<i class="nopaque-icons service-icons left" data-service="file-setup-pipeline"></i>File Setup')
|
|
||||||
def file_setup_pipeline():
|
def file_setup_pipeline():
|
||||||
service = 'file-setup-pipeline'
|
service = 'file-setup-pipeline'
|
||||||
service_manifest = SERVICES[service]
|
service_manifest = SERVICES[service]
|
||||||
@ -57,7 +53,7 @@ def file_setup_pipeline():
|
|||||||
abort(500)
|
abort(500)
|
||||||
job.status = JobStatus.SUBMITTED
|
job.status = JobStatus.SUBMITTED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
message = Markup(f'Job "<a href="{job.url}">{job.title}</a>" created')
|
message = f'Job "<a href="{job.url}">{job.title}</a>" created'
|
||||||
flash(message, 'job')
|
flash(message, 'job')
|
||||||
return {}, 201, {'Location': job.url}
|
return {}, 201, {'Location': job.url}
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -68,15 +64,12 @@ def file_setup_pipeline():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/tesseract-ocr-pipeline', methods=['GET', 'POST'])
|
@bp.route('/tesseract-ocr-pipeline', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.tesseract_ocr_pipeline', '<i class="nopaque-icons service-icons left" data-service="tesseract-ocr-pipeline"></i>Tesseract OCR Pipeline')
|
|
||||||
def tesseract_ocr_pipeline():
|
def tesseract_ocr_pipeline():
|
||||||
service_name = 'tesseract-ocr-pipeline'
|
service_name = 'tesseract-ocr-pipeline'
|
||||||
service_manifest = SERVICES[service_name]
|
service_manifest = SERVICES[service_name]
|
||||||
version = request.args.get('version', service_manifest['latest_version'])
|
version = request.args.get('version', service_manifest['latest_version'])
|
||||||
if version not in service_manifest['versions']:
|
if version not in service_manifest['versions']:
|
||||||
abort(404)
|
abort(404)
|
||||||
job_results = JobResult.query.all()
|
|
||||||
choosable_job_ids = [job_result.job.hashid for job_result in job_results if job_result.job.service == "file-setup-pipeline" and job_result.filename.endswith('.pdf')]
|
|
||||||
form = CreateTesseractOCRPipelineJobForm(prefix='create-job-form', version=version)
|
form = CreateTesseractOCRPipelineJobForm(prefix='create-job-form', version=version)
|
||||||
if form.is_submitted():
|
if form.is_submitted():
|
||||||
if not form.validate():
|
if not form.validate():
|
||||||
@ -103,7 +96,7 @@ def tesseract_ocr_pipeline():
|
|||||||
abort(500)
|
abort(500)
|
||||||
job.status = JobStatus.SUBMITTED
|
job.status = JobStatus.SUBMITTED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
message = Markup(f'Job "<a href="{job.url}">{job.title}</a>" created')
|
message = f'Job "<a href="{job.url}">{job.title}</a>" created'
|
||||||
flash(message, 'job')
|
flash(message, 'job')
|
||||||
return {}, 201, {'Location': job.url}
|
return {}, 201, {'Location': job.url}
|
||||||
tesseract_ocr_pipeline_models = [
|
tesseract_ocr_pipeline_models = [
|
||||||
@ -114,7 +107,6 @@ def tesseract_ocr_pipeline():
|
|||||||
return render_template(
|
return render_template(
|
||||||
'services/tesseract_ocr_pipeline.html.j2',
|
'services/tesseract_ocr_pipeline.html.j2',
|
||||||
title=service_manifest['name'],
|
title=service_manifest['name'],
|
||||||
choosable_job_ids=choosable_job_ids,
|
|
||||||
form=form,
|
form=form,
|
||||||
tesseract_ocr_pipeline_models=tesseract_ocr_pipeline_models,
|
tesseract_ocr_pipeline_models=tesseract_ocr_pipeline_models,
|
||||||
user_tesseract_ocr_pipeline_models_count=user_tesseract_ocr_pipeline_models_count
|
user_tesseract_ocr_pipeline_models_count=user_tesseract_ocr_pipeline_models_count
|
||||||
@ -122,7 +114,6 @@ def tesseract_ocr_pipeline():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/transkribus-htr-pipeline', methods=['GET', 'POST'])
|
@bp.route('/transkribus-htr-pipeline', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.transkribus_htr_pipeline', '<i class="nopaque-icons service-icons left" data-service="transkribus-htr-pipeline"></i>Transkribus HTR Pipeline')
|
|
||||||
def transkribus_htr_pipeline():
|
def transkribus_htr_pipeline():
|
||||||
if not current_app.config.get('NOPAQUE_TRANSKRIBUS_ENABLED'):
|
if not current_app.config.get('NOPAQUE_TRANSKRIBUS_ENABLED'):
|
||||||
abort(404)
|
abort(404)
|
||||||
@ -168,7 +159,7 @@ def transkribus_htr_pipeline():
|
|||||||
abort(500)
|
abort(500)
|
||||||
job.status = JobStatus.SUBMITTED
|
job.status = JobStatus.SUBMITTED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
message = Markup(f'Job "<a href="{job.url}">{job.title}</a>" created')
|
message = f'Job "<a href="{job.url}">{job.title}</a>" created'
|
||||||
flash(message, 'job')
|
flash(message, 'job')
|
||||||
return {}, 201, {'Location': job.url}
|
return {}, 201, {'Location': job.url}
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -180,7 +171,6 @@ def transkribus_htr_pipeline():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/spacy-nlp-pipeline', methods=['GET', 'POST'])
|
@bp.route('/spacy-nlp-pipeline', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.spacy_nlp_pipeline', '<i class="nopaque-icons service-icons left" data-service="spacy-nlp-pipeline"></i>SpaCy NLP Pipeline')
|
|
||||||
def spacy_nlp_pipeline():
|
def spacy_nlp_pipeline():
|
||||||
service = 'spacy-nlp-pipeline'
|
service = 'spacy-nlp-pipeline'
|
||||||
service_manifest = SERVICES[service]
|
service_manifest = SERVICES[service]
|
||||||
@ -214,7 +204,7 @@ def spacy_nlp_pipeline():
|
|||||||
abort(500)
|
abort(500)
|
||||||
job.status = JobStatus.SUBMITTED
|
job.status = JobStatus.SUBMITTED
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
message = Markup(f'Job "<a href="{job.url}">{job.title}</a>" created')
|
message = f'Job "<a href="{job.url}">{job.title}</a>" created'
|
||||||
flash(message, 'job')
|
flash(message, 'job')
|
||||||
return {}, 201, {'Location': job.url}
|
return {}, 201, {'Location': job.url}
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -227,7 +217,6 @@ def spacy_nlp_pipeline():
|
|||||||
|
|
||||||
|
|
||||||
@bp.route('/corpus-analysis')
|
@bp.route('/corpus-analysis')
|
||||||
@register_breadcrumb(bp, '.corpus_analysis', '<i class="nopaque-icons service-icons left" data-service="corpus-analysis"></i>Corpus Analysis')
|
|
||||||
def corpus_analysis():
|
def corpus_analysis():
|
||||||
return render_template(
|
return render_template(
|
||||||
'services/corpus_analysis.html.j2',
|
'services/corpus_analysis.html.j2',
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
from flask_breadcrumbs import register_breadcrumb
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from app.users.settings.routes import settings as settings_route
|
from app.users.settings.routes import settings as settings_route
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/settings', methods=['GET', 'POST'])
|
@bp.route('/settings', methods=['GET', 'POST'])
|
||||||
@register_breadcrumb(bp, '.', '<i class="material-icons left">settings</i>Settings')
|
|
||||||
def settings():
|
def settings():
|
||||||
g._nopaque_redirect_location_on_post = url_for('.settings')
|
g._nopaque_redirect_location_on_post = url_for('.settings')
|
||||||
return settings_route(current_user.id)
|
return settings_route(current_user.id)
|
||||||
|
@ -1,290 +0,0 @@
|
|||||||
/// Map deep get
|
|
||||||
/// @author Kitty Giraudel
|
|
||||||
/// @access public
|
|
||||||
/// @param {Map} $map - Map
|
|
||||||
/// @param {Arglist} $keys - Key chain
|
|
||||||
/// @return {*} - Desired value
|
|
||||||
@function map-deep-get($map, $keys...) {
|
|
||||||
@each $key in $keys {
|
|
||||||
$map: map-get($map, $key);
|
|
||||||
}
|
|
||||||
@return $map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$color: (
|
|
||||||
"baseline": (
|
|
||||||
"primary": #00426f,
|
|
||||||
"primary-variant": #1a5c89,
|
|
||||||
"secondary": #00426f,
|
|
||||||
"secondary-variant": #1a5c89,
|
|
||||||
"background": #ffffff,
|
|
||||||
"surface": #ffffff,
|
|
||||||
"error": #b00020
|
|
||||||
),
|
|
||||||
"social-area": (
|
|
||||||
"base": #d6ae86,
|
|
||||||
"darken": #C98536,
|
|
||||||
"lighten": #EAE2DB
|
|
||||||
),
|
|
||||||
"service": (
|
|
||||||
"corpus-analysis": (
|
|
||||||
"base": #aa9cc9,
|
|
||||||
"darken": #6b3f89,
|
|
||||||
"lighten": #ebe8f6
|
|
||||||
),
|
|
||||||
"file-setup-pipeline": (
|
|
||||||
"base": #d5dc95,
|
|
||||||
"darken": #a1b300,
|
|
||||||
"lighten": #f2f3e1
|
|
||||||
),
|
|
||||||
"spacy-nlp-pipeline": (
|
|
||||||
"base": #98acd2,
|
|
||||||
"darken": #0064a3,
|
|
||||||
"lighten": #e5e8f5
|
|
||||||
),
|
|
||||||
"tesseract-ocr-pipeline": (
|
|
||||||
"base": #a9d8c8,
|
|
||||||
"darken": #00a58b,
|
|
||||||
"lighten": #e7f4f1
|
|
||||||
),
|
|
||||||
"transkribus-htr-pipeline": (
|
|
||||||
"base": #607d8b,
|
|
||||||
"darken": #37474f,
|
|
||||||
"lighten": #cfd8dc
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"status": (
|
|
||||||
"corpus": (
|
|
||||||
"UNPREPARED": #9e9e9e,
|
|
||||||
"QUEUED": #2196f3,
|
|
||||||
"BUILDING": #ffc107,
|
|
||||||
"BUILT": #4caf50,
|
|
||||||
"FAILED": #f44336,
|
|
||||||
"STARTING_ANALYSIS_SESSION": #2196f3,
|
|
||||||
"RUNNING_ANALYSIS_SESSION": #4caf50,
|
|
||||||
"CANCELING_ANALYSIS_SESSION": #ff5722
|
|
||||||
),
|
|
||||||
"job": (
|
|
||||||
"INITIALIZING": #9e9e9e,
|
|
||||||
"SUBMITTED": #9e9e9e,
|
|
||||||
"QUEUED": #2196f3,
|
|
||||||
"RUNNING": #ffc107,
|
|
||||||
"CANCELING": #ff5722,
|
|
||||||
"CANCELED": #ff5722,
|
|
||||||
"COMPLETED": #4caf50,
|
|
||||||
"FAILED": #f44336
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"s-attr": (
|
|
||||||
"PERSON": #a6e22d,
|
|
||||||
"PER": #a6e22d,
|
|
||||||
"NORP": #ef60b4,
|
|
||||||
"FACILITY": #43c6fc,
|
|
||||||
"ORG": #43c6fc,
|
|
||||||
"GPE": #fd9720,
|
|
||||||
"LOC": #fd9720,
|
|
||||||
"PRODUCT": #a99dfb,
|
|
||||||
"MISC": #a99dfb,
|
|
||||||
"EVENT": #fc0,
|
|
||||||
"WORK_OF_ART": #fc0,
|
|
||||||
"LANGUAGE": #fc0,
|
|
||||||
"DATE": #2fbbab,
|
|
||||||
"TIME": #2fbbab,
|
|
||||||
"PERCENT": #bbb,
|
|
||||||
"MONEY": #bbb,
|
|
||||||
"QUANTITY": #bbb,
|
|
||||||
"ORDINAL": #bbb,
|
|
||||||
"CARDINAL": #bbb
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
@each $key, $color-code in map-get($color, "baseline") {
|
|
||||||
.#{$key}-color {
|
|
||||||
background-color: $color-code !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$key}-color-border {
|
|
||||||
border-color: $color-code !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$key}-color-text {
|
|
||||||
color: $color-code !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@each $key, $color-code in map-get($color, "social-area") {
|
|
||||||
.social-area-color-#{$key} {
|
|
||||||
background-color: $color-code !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-area-color-border-#{$key} {
|
|
||||||
border-color: $color-code !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@each $service-name, $color-palette in map-get($color, "service") {
|
|
||||||
.service-color[data-service="#{$service-name}"] {
|
|
||||||
background-color: map-get($color-palette, "base") !important;
|
|
||||||
|
|
||||||
&.darken {
|
|
||||||
background-color: map-get($color-palette, "darken") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.lighten {
|
|
||||||
background-color: map-get($color-palette, "lighten") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-color-border[data-service="#{$service-name}"] {
|
|
||||||
border-color: map-get($color-palette, "base") !important;
|
|
||||||
|
|
||||||
&.border-darken {
|
|
||||||
border-color: map-get($color-palette, "darken") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.border-lighten {
|
|
||||||
border-color: map-get($color-palette, "lighten") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-color-text[data-service="#{$service-name}"] {
|
|
||||||
color: map-get($color-palette, "base") !important;
|
|
||||||
|
|
||||||
&.text-darken {
|
|
||||||
color: map-get($color-palette, "darken") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.text-lighten {
|
|
||||||
color: map-get($color-palette, "lighten") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-scheme[data-service="#{$service-name}"] {
|
|
||||||
background-color: map-get($color-palette, "lighten");
|
|
||||||
|
|
||||||
.btn, .btn-small, .btn-large, .btn-floating {
|
|
||||||
background-color: map-get($color-palette, "darken");
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: map-get($color-palette, "base");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
li.active {
|
|
||||||
background-color: map-get($color-palette, "darken");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-of-contents {
|
|
||||||
a.active, a:hover {
|
|
||||||
border-color: map-get($color-palette, "darken");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
.tab {
|
|
||||||
&.disabled {
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: change-color(map-get($color-palette, "darken"), $alpha: 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:focus, &:hover, &.active {
|
|
||||||
color: map-get($color-palette, "darken");
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus, &.active, &.active:focus {
|
|
||||||
background-color: change-color(map-get($color-palette, "darken"), $alpha: 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.indicator {
|
|
||||||
background-color: map-get($color-palette, "darken");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@each $ressource-name, $color-palette in map-get($color, "status") {
|
|
||||||
@each $key, $color-code in $color-palette {
|
|
||||||
.#{$ressource-name}-status-color[data-status="#{$key}"] {
|
|
||||||
background-color: $color-code !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$ressource-name}-status-color-border[data-status="#{$key}"] {
|
|
||||||
border-color: $color-code !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$ressource-name}-status-color-text[data-status="#{$key}"] {
|
|
||||||
color: $color-code !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@each $key, $color-code in map-get($color, "s-attr") {
|
|
||||||
.chip.s-attr[data-s-attr-type="ent"][data-s-attr-ent-type="#{$key}"] {
|
|
||||||
background-color: $color-code !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main {
|
|
||||||
.btn, .btn-small, .btn-large, .btn-floating {
|
|
||||||
background-color: map-deep-get($color, "baseline", "secondary");
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: map-deep-get($color, "baseline", "secondary-variant");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
li.active {
|
|
||||||
background-color: map-deep-get($color, "baseline", "secondary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-of-contents {
|
|
||||||
a.active, a:hover {
|
|
||||||
border-color: map-deep-get($color, "baseline", "secondary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
.tab {
|
|
||||||
&.disabled {
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: change-color(map-deep-get($color, "baseline", "secondary"), $alpha: 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:focus, &:hover, &.active {
|
|
||||||
color: map-deep-get($color, "baseline", "secondary");
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus, &.active, &.active:focus {
|
|
||||||
background-color: change-color(map-deep-get($color, "baseline", "secondary"), $alpha: 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.indicator {
|
|
||||||
background-color: map-deep-get($color, "baseline", "secondary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
.parallax-container .parallax {
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-content {
|
|
||||||
width: 100% !important;
|
|
||||||
left: 0 !important;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
* The sidenav-fixed class is used which causes the sidenav to be fixed and open
|
|
||||||
* on large screens and hides to the regular functionality on smaller screens.
|
|
||||||
* In order to prevent the sidenav to overlap the content, the content (in our
|
|
||||||
* case header, main and footer) gets an offset equal to the width of the
|
|
||||||
* sidenav.
|
|
||||||
*/
|
|
||||||
@media only screen and (min-width : 993px) {
|
|
||||||
header, main, footer {padding-left: 300px;}
|
|
||||||
.modal:not(.bottom-sheet) {left: 300px;}
|
|
||||||
.navbar-fixed > nav {width: calc(100% - 300px)}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
/* Change navbar height bacause an extended and fixed navbar is used */
|
|
||||||
.navbar-fixed {
|
|
||||||
height: 112px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Change placholdertext color of file uplaod fields */
|
|
||||||
::placeholder {
|
|
||||||
color: #9e9e9e;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* changes preoloader size etc. to fit visually better with the chip status
|
|
||||||
* indicator of jobs
|
|
||||||
*/
|
|
||||||
.status-spinner {
|
|
||||||
margin-bottom: -10px;
|
|
||||||
width: 30px !important;
|
|
||||||
height: 30px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#manual-modal .manual-chapter-title {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show-if-only-child:not(:only-child) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-scale-x2 {
|
|
||||||
transform: scale(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-scale-x2 .nopaque-icons.service-icons {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fix material icon vertical alignment when nested in various elements */
|
|
||||||
h1 .nopaque-icons, h2 .nopaque-icons, h3 .nopaque-icons, h4 .nopaque-icons, .tab .nopaque-icons, .tab .material-icons {
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.corpus-status-text, .job-status-text {text-transform: lowercase;}
|
|
||||||
.corpus-status-text[data-status]:empty::before, .job-status-text[data-status]:empty::before {content: attr(data-status);}
|
|
||||||
|
|
||||||
.service-scheme[data-service="file-setup-pipeline"] .nopaque-icons.service-icons[data-service="inherit"]:empty::before {content: "E";}
|
|
||||||
.service-scheme[data-service="tesseract-ocr-pipeline"] .nopaque-icons.service-icons[data-service="inherit"]:empty::before {content: "F";}
|
|
||||||
.service-scheme[data-service="transkribus-htr-pipeline"] .nopaque-icons.service-icons[data-service="inherit"]:empty::before {content: "F";}
|
|
||||||
.service-scheme[data-service="spacy-nlp-pipeline"] .nopaque-icons.service-icons[data-service="inherit"]:empty::before {content: "G";}
|
|
||||||
.service-scheme[data-service="corpus-analysis"] .nopaque-icons.service-icons[data-service="inherit"]:empty::before {content: "H";}
|
|
||||||
|
|
||||||
.nopaque-icons.service-icons[data-service="file-setup-pipeline"]:empty::before {content: "E";}
|
|
||||||
.nopaque-icons.service-icons[data-service="tesseract-ocr-pipeline"]:empty::before {content: "F";}
|
|
||||||
.nopaque-icons.service-icons[data-service="transkribus-htr-pipeline"]:empty::before {content: "F";}
|
|
||||||
.nopaque-icons.service-icons[data-service="spacy-nlp-pipeline"]:empty::before {content: "G";}
|
|
||||||
.nopaque-icons.service-icons[data-service="corpus-analysis"]:empty::before {content: "H";}
|
|
||||||
|
|
||||||
[draggable="true"] {cursor: move !important;}
|
|
||||||
.clickable {cursor: pointer !important;}
|
|
||||||
.chip.s-attr .chip.p-attr {background-color: inherit;}
|
|
||||||
|
|
||||||
|
|
||||||
.width-25 {width: 25%;}
|
|
||||||
.width-50 {width: 50%;}
|
|
||||||
.width-75 {width: 75%;}
|
|
||||||
.width-100 {width: 100%;}
|
|
22
app/static/external/JSON-Patch/LICENSE
vendored
Normal file
22
app/static/external/JSON-Patch/LICENSE
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2013, 2014, 2020 Joachim Wester
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
app/static/external/JSON-Patch/VERSION
vendored
Normal file
1
app/static/external/JSON-Patch/VERSION
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.1.1
|
928
app/static/external/JSON-Patch/js/fast-json-patch.js
vendored
Normal file
928
app/static/external/JSON-Patch/js/fast-json-patch.js
vendored
Normal file
@ -0,0 +1,928 @@
|
|||||||
|
/*! fast-json-patch, version: 3.1.1 */
|
||||||
|
var jsonpatch =
|
||||||
|
/******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // define __esModule on exports
|
||||||
|
/******/ __webpack_require__.r = function(exports) {
|
||||||
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||||
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
/******/ }
|
||||||
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // create a fake namespace object
|
||||||
|
/******/ // mode & 1: value is a module id, require it
|
||||||
|
/******/ // mode & 2: merge all properties of value into the ns
|
||||||
|
/******/ // mode & 4: return value when already ns object
|
||||||
|
/******/ // mode & 8|1: behave like require
|
||||||
|
/******/ __webpack_require__.t = function(value, mode) {
|
||||||
|
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||||
|
/******/ if(mode & 8) return value;
|
||||||
|
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||||
|
/******/ var ns = Object.create(null);
|
||||||
|
/******/ __webpack_require__.r(ns);
|
||||||
|
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||||
|
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||||
|
/******/ return ns;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "";
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(__webpack_require__.s = 2);
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ([
|
||||||
|
/* 0 */
|
||||||
|
/***/ (function(module, exports) {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||||
|
* (c) 2017-2022 Joachim Wester
|
||||||
|
* MIT licensed
|
||||||
|
*/
|
||||||
|
var __extends = (this && this.__extends) || (function () {
|
||||||
|
var extendStatics = function (d, b) {
|
||||||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||||
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||||
|
return extendStatics(d, b);
|
||||||
|
};
|
||||||
|
return function (d, b) {
|
||||||
|
extendStatics(d, b);
|
||||||
|
function __() { this.constructor = d; }
|
||||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var _hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
function hasOwnProperty(obj, key) {
|
||||||
|
return _hasOwnProperty.call(obj, key);
|
||||||
|
}
|
||||||
|
exports.hasOwnProperty = hasOwnProperty;
|
||||||
|
function _objectKeys(obj) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
var keys_1 = new Array(obj.length);
|
||||||
|
for (var k = 0; k < keys_1.length; k++) {
|
||||||
|
keys_1[k] = "" + k;
|
||||||
|
}
|
||||||
|
return keys_1;
|
||||||
|
}
|
||||||
|
if (Object.keys) {
|
||||||
|
return Object.keys(obj);
|
||||||
|
}
|
||||||
|
var keys = [];
|
||||||
|
for (var i in obj) {
|
||||||
|
if (hasOwnProperty(obj, i)) {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
exports._objectKeys = _objectKeys;
|
||||||
|
;
|
||||||
|
/**
|
||||||
|
* Deeply clone the object.
|
||||||
|
* https://jsperf.com/deep-copy-vs-json-stringify-json-parse/25 (recursiveDeepCopy)
|
||||||
|
* @param {any} obj value to clone
|
||||||
|
* @return {any} cloned obj
|
||||||
|
*/
|
||||||
|
function _deepClone(obj) {
|
||||||
|
switch (typeof obj) {
|
||||||
|
case "object":
|
||||||
|
return JSON.parse(JSON.stringify(obj)); //Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5
|
||||||
|
case "undefined":
|
||||||
|
return null; //this is how JSON.stringify behaves for array items
|
||||||
|
default:
|
||||||
|
return obj; //no need to clone primitives
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports._deepClone = _deepClone;
|
||||||
|
//3x faster than cached /^\d+$/.test(str)
|
||||||
|
function isInteger(str) {
|
||||||
|
var i = 0;
|
||||||
|
var len = str.length;
|
||||||
|
var charCode;
|
||||||
|
while (i < len) {
|
||||||
|
charCode = str.charCodeAt(i);
|
||||||
|
if (charCode >= 48 && charCode <= 57) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
exports.isInteger = isInteger;
|
||||||
|
/**
|
||||||
|
* Escapes a json pointer path
|
||||||
|
* @param path The raw pointer
|
||||||
|
* @return the Escaped path
|
||||||
|
*/
|
||||||
|
function escapePathComponent(path) {
|
||||||
|
if (path.indexOf('/') === -1 && path.indexOf('~') === -1)
|
||||||
|
return path;
|
||||||
|
return path.replace(/~/g, '~0').replace(/\//g, '~1');
|
||||||
|
}
|
||||||
|
exports.escapePathComponent = escapePathComponent;
|
||||||
|
/**
|
||||||
|
* Unescapes a json pointer path
|
||||||
|
* @param path The escaped pointer
|
||||||
|
* @return The unescaped path
|
||||||
|
*/
|
||||||
|
function unescapePathComponent(path) {
|
||||||
|
return path.replace(/~1/g, '/').replace(/~0/g, '~');
|
||||||
|
}
|
||||||
|
exports.unescapePathComponent = unescapePathComponent;
|
||||||
|
function _getPathRecursive(root, obj) {
|
||||||
|
var found;
|
||||||
|
for (var key in root) {
|
||||||
|
if (hasOwnProperty(root, key)) {
|
||||||
|
if (root[key] === obj) {
|
||||||
|
return escapePathComponent(key) + '/';
|
||||||
|
}
|
||||||
|
else if (typeof root[key] === 'object') {
|
||||||
|
found = _getPathRecursive(root[key], obj);
|
||||||
|
if (found != '') {
|
||||||
|
return escapePathComponent(key) + '/' + found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
exports._getPathRecursive = _getPathRecursive;
|
||||||
|
function getPath(root, obj) {
|
||||||
|
if (root === obj) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
var path = _getPathRecursive(root, obj);
|
||||||
|
if (path === '') {
|
||||||
|
throw new Error("Object not found in root");
|
||||||
|
}
|
||||||
|
return "/" + path;
|
||||||
|
}
|
||||||
|
exports.getPath = getPath;
|
||||||
|
/**
|
||||||
|
* Recursively checks whether an object has any undefined values inside.
|
||||||
|
*/
|
||||||
|
function hasUndefined(obj) {
|
||||||
|
if (obj === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
for (var i_1 = 0, len = obj.length; i_1 < len; i_1++) {
|
||||||
|
if (hasUndefined(obj[i_1])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (typeof obj === "object") {
|
||||||
|
var objKeys = _objectKeys(obj);
|
||||||
|
var objKeysLength = objKeys.length;
|
||||||
|
for (var i = 0; i < objKeysLength; i++) {
|
||||||
|
if (hasUndefined(obj[objKeys[i]])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
exports.hasUndefined = hasUndefined;
|
||||||
|
function patchErrorMessageFormatter(message, args) {
|
||||||
|
var messageParts = [message];
|
||||||
|
for (var key in args) {
|
||||||
|
var value = typeof args[key] === 'object' ? JSON.stringify(args[key], null, 2) : args[key]; // pretty print
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
messageParts.push(key + ": " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageParts.join('\n');
|
||||||
|
}
|
||||||
|
var PatchError = /** @class */ (function (_super) {
|
||||||
|
__extends(PatchError, _super);
|
||||||
|
function PatchError(message, name, index, operation, tree) {
|
||||||
|
var _newTarget = this.constructor;
|
||||||
|
var _this = _super.call(this, patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree })) || this;
|
||||||
|
_this.name = name;
|
||||||
|
_this.index = index;
|
||||||
|
_this.operation = operation;
|
||||||
|
_this.tree = tree;
|
||||||
|
Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain, see https://stackoverflow.com/a/48342359
|
||||||
|
_this.message = patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree });
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
return PatchError;
|
||||||
|
}(Error));
|
||||||
|
exports.PatchError = PatchError;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 1 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
var helpers_js_1 = __webpack_require__(0);
|
||||||
|
exports.JsonPatchError = helpers_js_1.PatchError;
|
||||||
|
exports.deepClone = helpers_js_1._deepClone;
|
||||||
|
/* We use a Javascript hash to store each
|
||||||
|
function. Each hash entry (property) uses
|
||||||
|
the operation identifiers specified in rfc6902.
|
||||||
|
In this way, we can map each patch operation
|
||||||
|
to its dedicated function in efficient way.
|
||||||
|
*/
|
||||||
|
/* The operations applicable to an object */
|
||||||
|
var objOps = {
|
||||||
|
add: function (obj, key, document) {
|
||||||
|
obj[key] = this.value;
|
||||||
|
return { newDocument: document };
|
||||||
|
},
|
||||||
|
remove: function (obj, key, document) {
|
||||||
|
var removed = obj[key];
|
||||||
|
delete obj[key];
|
||||||
|
return { newDocument: document, removed: removed };
|
||||||
|
},
|
||||||
|
replace: function (obj, key, document) {
|
||||||
|
var removed = obj[key];
|
||||||
|
obj[key] = this.value;
|
||||||
|
return { newDocument: document, removed: removed };
|
||||||
|
},
|
||||||
|
move: function (obj, key, document) {
|
||||||
|
/* in case move target overwrites an existing value,
|
||||||
|
return the removed value, this can be taxing performance-wise,
|
||||||
|
and is potentially unneeded */
|
||||||
|
var removed = getValueByPointer(document, this.path);
|
||||||
|
if (removed) {
|
||||||
|
removed = helpers_js_1._deepClone(removed);
|
||||||
|
}
|
||||||
|
var originalValue = applyOperation(document, { op: "remove", path: this.from }).removed;
|
||||||
|
applyOperation(document, { op: "add", path: this.path, value: originalValue });
|
||||||
|
return { newDocument: document, removed: removed };
|
||||||
|
},
|
||||||
|
copy: function (obj, key, document) {
|
||||||
|
var valueToCopy = getValueByPointer(document, this.from);
|
||||||
|
// enforce copy by value so further operations don't affect source (see issue #177)
|
||||||
|
applyOperation(document, { op: "add", path: this.path, value: helpers_js_1._deepClone(valueToCopy) });
|
||||||
|
return { newDocument: document };
|
||||||
|
},
|
||||||
|
test: function (obj, key, document) {
|
||||||
|
return { newDocument: document, test: _areEquals(obj[key], this.value) };
|
||||||
|
},
|
||||||
|
_get: function (obj, key, document) {
|
||||||
|
this.value = obj[key];
|
||||||
|
return { newDocument: document };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/* The operations applicable to an array. Many are the same as for the object */
|
||||||
|
var arrOps = {
|
||||||
|
add: function (arr, i, document) {
|
||||||
|
if (helpers_js_1.isInteger(i)) {
|
||||||
|
arr.splice(i, 0, this.value);
|
||||||
|
}
|
||||||
|
else { // array props
|
||||||
|
arr[i] = this.value;
|
||||||
|
}
|
||||||
|
// this may be needed when using '-' in an array
|
||||||
|
return { newDocument: document, index: i };
|
||||||
|
},
|
||||||
|
remove: function (arr, i, document) {
|
||||||
|
var removedList = arr.splice(i, 1);
|
||||||
|
return { newDocument: document, removed: removedList[0] };
|
||||||
|
},
|
||||||
|
replace: function (arr, i, document) {
|
||||||
|
var removed = arr[i];
|
||||||
|
arr[i] = this.value;
|
||||||
|
return { newDocument: document, removed: removed };
|
||||||
|
},
|
||||||
|
move: objOps.move,
|
||||||
|
copy: objOps.copy,
|
||||||
|
test: objOps.test,
|
||||||
|
_get: objOps._get
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Retrieves a value from a JSON document by a JSON pointer.
|
||||||
|
* Returns the value.
|
||||||
|
*
|
||||||
|
* @param document The document to get the value from
|
||||||
|
* @param pointer an escaped JSON pointer
|
||||||
|
* @return The retrieved value
|
||||||
|
*/
|
||||||
|
function getValueByPointer(document, pointer) {
|
||||||
|
if (pointer == '') {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
var getOriginalDestination = { op: "_get", path: pointer };
|
||||||
|
applyOperation(document, getOriginalDestination);
|
||||||
|
return getOriginalDestination.value;
|
||||||
|
}
|
||||||
|
exports.getValueByPointer = getValueByPointer;
|
||||||
|
/**
|
||||||
|
* Apply a single JSON Patch Operation on a JSON document.
|
||||||
|
* Returns the {newDocument, result} of the operation.
|
||||||
|
* It modifies the `document` and `operation` objects - it gets the values by reference.
|
||||||
|
* If you would like to avoid touching your values, clone them:
|
||||||
|
* `jsonpatch.applyOperation(document, jsonpatch._deepClone(operation))`.
|
||||||
|
*
|
||||||
|
* @param document The document to patch
|
||||||
|
* @param operation The operation to apply
|
||||||
|
* @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation.
|
||||||
|
* @param mutateDocument Whether to mutate the original document or clone it before applying
|
||||||
|
* @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`.
|
||||||
|
* @return `{newDocument, result}` after the operation
|
||||||
|
*/
|
||||||
|
function applyOperation(document, operation, validateOperation, mutateDocument, banPrototypeModifications, index) {
|
||||||
|
if (validateOperation === void 0) { validateOperation = false; }
|
||||||
|
if (mutateDocument === void 0) { mutateDocument = true; }
|
||||||
|
if (banPrototypeModifications === void 0) { banPrototypeModifications = true; }
|
||||||
|
if (index === void 0) { index = 0; }
|
||||||
|
if (validateOperation) {
|
||||||
|
if (typeof validateOperation == 'function') {
|
||||||
|
validateOperation(operation, 0, document, operation.path);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
validator(operation, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ROOT OPERATIONS */
|
||||||
|
if (operation.path === "") {
|
||||||
|
var returnValue = { newDocument: document };
|
||||||
|
if (operation.op === 'add') {
|
||||||
|
returnValue.newDocument = operation.value;
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
else if (operation.op === 'replace') {
|
||||||
|
returnValue.newDocument = operation.value;
|
||||||
|
returnValue.removed = document; //document we removed
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
else if (operation.op === 'move' || operation.op === 'copy') { // it's a move or copy to root
|
||||||
|
returnValue.newDocument = getValueByPointer(document, operation.from); // get the value by json-pointer in `from` field
|
||||||
|
if (operation.op === 'move') { // report removed item
|
||||||
|
returnValue.removed = document;
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
else if (operation.op === 'test') {
|
||||||
|
returnValue.test = _areEquals(document, operation.value);
|
||||||
|
if (returnValue.test === false) {
|
||||||
|
throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document);
|
||||||
|
}
|
||||||
|
returnValue.newDocument = document;
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
else if (operation.op === 'remove') { // a remove on root
|
||||||
|
returnValue.removed = document;
|
||||||
|
returnValue.newDocument = null;
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
else if (operation.op === '_get') {
|
||||||
|
operation.value = document;
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
else { /* bad operation */
|
||||||
|
if (validateOperation) {
|
||||||
|
throw new exports.JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, document);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} /* END ROOT OPERATIONS */
|
||||||
|
else {
|
||||||
|
if (!mutateDocument) {
|
||||||
|
document = helpers_js_1._deepClone(document);
|
||||||
|
}
|
||||||
|
var path = operation.path || "";
|
||||||
|
var keys = path.split('/');
|
||||||
|
var obj = document;
|
||||||
|
var t = 1; //skip empty element - http://jsperf.com/to-shift-or-not-to-shift
|
||||||
|
var len = keys.length;
|
||||||
|
var existingPathFragment = undefined;
|
||||||
|
var key = void 0;
|
||||||
|
var validateFunction = void 0;
|
||||||
|
if (typeof validateOperation == 'function') {
|
||||||
|
validateFunction = validateOperation;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
validateFunction = validator;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
key = keys[t];
|
||||||
|
if (key && key.indexOf('~') != -1) {
|
||||||
|
key = helpers_js_1.unescapePathComponent(key);
|
||||||
|
}
|
||||||
|
if (banPrototypeModifications &&
|
||||||
|
(key == '__proto__' ||
|
||||||
|
(key == 'prototype' && t > 0 && keys[t - 1] == 'constructor'))) {
|
||||||
|
throw new TypeError('JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README');
|
||||||
|
}
|
||||||
|
if (validateOperation) {
|
||||||
|
if (existingPathFragment === undefined) {
|
||||||
|
if (obj[key] === undefined) {
|
||||||
|
existingPathFragment = keys.slice(0, t).join('/');
|
||||||
|
}
|
||||||
|
else if (t == len - 1) {
|
||||||
|
existingPathFragment = operation.path;
|
||||||
|
}
|
||||||
|
if (existingPathFragment !== undefined) {
|
||||||
|
validateFunction(operation, 0, document, existingPathFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t++;
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (key === '-') {
|
||||||
|
key = obj.length;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (validateOperation && !helpers_js_1.isInteger(key)) {
|
||||||
|
throw new exports.JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index", "OPERATION_PATH_ILLEGAL_ARRAY_INDEX", index, operation, document);
|
||||||
|
} // only parse key when it's an integer for `arr.prop` to work
|
||||||
|
else if (helpers_js_1.isInteger(key)) {
|
||||||
|
key = ~~key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t >= len) {
|
||||||
|
if (validateOperation && operation.op === "add" && key > obj.length) {
|
||||||
|
throw new exports.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array", "OPERATION_VALUE_OUT_OF_BOUNDS", index, operation, document);
|
||||||
|
}
|
||||||
|
var returnValue = arrOps[operation.op].call(operation, obj, key, document); // Apply patch
|
||||||
|
if (returnValue.test === false) {
|
||||||
|
throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document);
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (t >= len) {
|
||||||
|
var returnValue = objOps[operation.op].call(operation, obj, key, document); // Apply patch
|
||||||
|
if (returnValue.test === false) {
|
||||||
|
throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document);
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj = obj[key];
|
||||||
|
// If we have more keys in the path, but the next value isn't a non-null object,
|
||||||
|
// throw an OPERATION_PATH_UNRESOLVABLE error instead of iterating again.
|
||||||
|
if (validateOperation && t < len && (!obj || typeof obj !== "object")) {
|
||||||
|
throw new exports.JsonPatchError('Cannot perform operation at the desired path', 'OPERATION_PATH_UNRESOLVABLE', index, operation, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.applyOperation = applyOperation;
|
||||||
|
/**
|
||||||
|
* Apply a full JSON Patch array on a JSON document.
|
||||||
|
* Returns the {newDocument, result} of the patch.
|
||||||
|
* It modifies the `document` object and `patch` - it gets the values by reference.
|
||||||
|
* If you would like to avoid touching your values, clone them:
|
||||||
|
* `jsonpatch.applyPatch(document, jsonpatch._deepClone(patch))`.
|
||||||
|
*
|
||||||
|
* @param document The document to patch
|
||||||
|
* @param patch The patch to apply
|
||||||
|
* @param validateOperation `false` is without validation, `true` to use default jsonpatch's validation, or you can pass a `validateOperation` callback to be used for validation.
|
||||||
|
* @param mutateDocument Whether to mutate the original document or clone it before applying
|
||||||
|
* @param banPrototypeModifications Whether to ban modifications to `__proto__`, defaults to `true`.
|
||||||
|
* @return An array of `{newDocument, result}` after the patch
|
||||||
|
*/
|
||||||
|
function applyPatch(document, patch, validateOperation, mutateDocument, banPrototypeModifications) {
|
||||||
|
if (mutateDocument === void 0) { mutateDocument = true; }
|
||||||
|
if (banPrototypeModifications === void 0) { banPrototypeModifications = true; }
|
||||||
|
if (validateOperation) {
|
||||||
|
if (!Array.isArray(patch)) {
|
||||||
|
throw new exports.JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mutateDocument) {
|
||||||
|
document = helpers_js_1._deepClone(document);
|
||||||
|
}
|
||||||
|
var results = new Array(patch.length);
|
||||||
|
for (var i = 0, length_1 = patch.length; i < length_1; i++) {
|
||||||
|
// we don't need to pass mutateDocument argument because if it was true, we already deep cloned the object, we'll just pass `true`
|
||||||
|
results[i] = applyOperation(document, patch[i], validateOperation, true, banPrototypeModifications, i);
|
||||||
|
document = results[i].newDocument; // in case root was replaced
|
||||||
|
}
|
||||||
|
results.newDocument = document;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
exports.applyPatch = applyPatch;
|
||||||
|
/**
|
||||||
|
* Apply a single JSON Patch Operation on a JSON document.
|
||||||
|
* Returns the updated document.
|
||||||
|
* Suitable as a reducer.
|
||||||
|
*
|
||||||
|
* @param document The document to patch
|
||||||
|
* @param operation The operation to apply
|
||||||
|
* @return The updated document
|
||||||
|
*/
|
||||||
|
function applyReducer(document, operation, index) {
|
||||||
|
var operationResult = applyOperation(document, operation);
|
||||||
|
if (operationResult.test === false) { // failed test
|
||||||
|
throw new exports.JsonPatchError("Test operation failed", 'TEST_OPERATION_FAILED', index, operation, document);
|
||||||
|
}
|
||||||
|
return operationResult.newDocument;
|
||||||
|
}
|
||||||
|
exports.applyReducer = applyReducer;
|
||||||
|
/**
|
||||||
|
* Validates a single operation. Called from `jsonpatch.validate`. Throws `JsonPatchError` in case of an error.
|
||||||
|
* @param {object} operation - operation object (patch)
|
||||||
|
* @param {number} index - index of operation in the sequence
|
||||||
|
* @param {object} [document] - object where the operation is supposed to be applied
|
||||||
|
* @param {string} [existingPathFragment] - comes along with `document`
|
||||||
|
*/
|
||||||
|
function validator(operation, index, document, existingPathFragment) {
|
||||||
|
if (typeof operation !== 'object' || operation === null || Array.isArray(operation)) {
|
||||||
|
throw new exports.JsonPatchError('Operation is not an object', 'OPERATION_NOT_AN_OBJECT', index, operation, document);
|
||||||
|
}
|
||||||
|
else if (!objOps[operation.op]) {
|
||||||
|
throw new exports.JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, document);
|
||||||
|
}
|
||||||
|
else if (typeof operation.path !== 'string') {
|
||||||
|
throw new exports.JsonPatchError('Operation `path` property is not a string', 'OPERATION_PATH_INVALID', index, operation, document);
|
||||||
|
}
|
||||||
|
else if (operation.path.indexOf('/') !== 0 && operation.path.length > 0) {
|
||||||
|
// paths that aren't empty string should start with "/"
|
||||||
|
throw new exports.JsonPatchError('Operation `path` property must start with "/"', 'OPERATION_PATH_INVALID', index, operation, document);
|
||||||
|
}
|
||||||
|
else if ((operation.op === 'move' || operation.op === 'copy') && typeof operation.from !== 'string') {
|
||||||
|
throw new exports.JsonPatchError('Operation `from` property is not present (applicable in `move` and `copy` operations)', 'OPERATION_FROM_REQUIRED', index, operation, document);
|
||||||
|
}
|
||||||
|
else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && operation.value === undefined) {
|
||||||
|
throw new exports.JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_REQUIRED', index, operation, document);
|
||||||
|
}
|
||||||
|
else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && helpers_js_1.hasUndefined(operation.value)) {
|
||||||
|
throw new exports.JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED', index, operation, document);
|
||||||
|
}
|
||||||
|
else if (document) {
|
||||||
|
if (operation.op == "add") {
|
||||||
|
var pathLen = operation.path.split("/").length;
|
||||||
|
var existingPathLen = existingPathFragment.split("/").length;
|
||||||
|
if (pathLen !== existingPathLen + 1 && pathLen !== existingPathLen) {
|
||||||
|
throw new exports.JsonPatchError('Cannot perform an `add` operation at the desired path', 'OPERATION_PATH_CANNOT_ADD', index, operation, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation.op === 'replace' || operation.op === 'remove' || operation.op === '_get') {
|
||||||
|
if (operation.path !== existingPathFragment) {
|
||||||
|
throw new exports.JsonPatchError('Cannot perform the operation at a path that does not exist', 'OPERATION_PATH_UNRESOLVABLE', index, operation, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation.op === 'move' || operation.op === 'copy') {
|
||||||
|
var existingValue = { op: "_get", path: operation.from, value: undefined };
|
||||||
|
var error = validate([existingValue], document);
|
||||||
|
if (error && error.name === 'OPERATION_PATH_UNRESOLVABLE') {
|
||||||
|
throw new exports.JsonPatchError('Cannot perform the operation from a path that does not exist', 'OPERATION_FROM_UNRESOLVABLE', index, operation, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.validator = validator;
|
||||||
|
/**
|
||||||
|
* Validates a sequence of operations. If `document` parameter is provided, the sequence is additionally validated against the object document.
|
||||||
|
* If error is encountered, returns a JsonPatchError object
|
||||||
|
* @param sequence
|
||||||
|
* @param document
|
||||||
|
* @returns {JsonPatchError|undefined}
|
||||||
|
*/
|
||||||
|
function validate(sequence, document, externalValidator) {
|
||||||
|
try {
|
||||||
|
if (!Array.isArray(sequence)) {
|
||||||
|
throw new exports.JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY');
|
||||||
|
}
|
||||||
|
if (document) {
|
||||||
|
//clone document and sequence so that we can safely try applying operations
|
||||||
|
applyPatch(helpers_js_1._deepClone(document), helpers_js_1._deepClone(sequence), externalValidator || true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
externalValidator = externalValidator || validator;
|
||||||
|
for (var i = 0; i < sequence.length; i++) {
|
||||||
|
externalValidator(sequence[i], i, document, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e instanceof exports.JsonPatchError) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.validate = validate;
|
||||||
|
// based on https://github.com/epoberezkin/fast-deep-equal
|
||||||
|
// MIT License
|
||||||
|
// Copyright (c) 2017 Evgeny Poberezkin
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
function _areEquals(a, b) {
|
||||||
|
if (a === b)
|
||||||
|
return true;
|
||||||
|
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
||||||
|
var arrA = Array.isArray(a), arrB = Array.isArray(b), i, length, key;
|
||||||
|
if (arrA && arrB) {
|
||||||
|
length = a.length;
|
||||||
|
if (length != b.length)
|
||||||
|
return false;
|
||||||
|
for (i = length; i-- !== 0;)
|
||||||
|
if (!_areEquals(a[i], b[i]))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (arrA != arrB)
|
||||||
|
return false;
|
||||||
|
var keys = Object.keys(a);
|
||||||
|
length = keys.length;
|
||||||
|
if (length !== Object.keys(b).length)
|
||||||
|
return false;
|
||||||
|
for (i = length; i-- !== 0;)
|
||||||
|
if (!b.hasOwnProperty(keys[i]))
|
||||||
|
return false;
|
||||||
|
for (i = length; i-- !== 0;) {
|
||||||
|
key = keys[i];
|
||||||
|
if (!_areEquals(a[key], b[key]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return a !== a && b !== b;
|
||||||
|
}
|
||||||
|
exports._areEquals = _areEquals;
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 2 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
var core = __webpack_require__(1);
|
||||||
|
Object.assign(exports, core);
|
||||||
|
|
||||||
|
var duplex = __webpack_require__(3);
|
||||||
|
Object.assign(exports, duplex);
|
||||||
|
|
||||||
|
var helpers = __webpack_require__(0);
|
||||||
|
exports.JsonPatchError = helpers.PatchError;
|
||||||
|
exports.deepClone = helpers._deepClone;
|
||||||
|
exports.escapePathComponent = helpers.escapePathComponent;
|
||||||
|
exports.unescapePathComponent = helpers.unescapePathComponent;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 3 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/*!
|
||||||
|
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||||
|
* (c) 2017-2021 Joachim Wester
|
||||||
|
* MIT license
|
||||||
|
*/
|
||||||
|
var helpers_js_1 = __webpack_require__(0);
|
||||||
|
var core_js_1 = __webpack_require__(1);
|
||||||
|
var beforeDict = new WeakMap();
|
||||||
|
var Mirror = /** @class */ (function () {
|
||||||
|
function Mirror(obj) {
|
||||||
|
this.observers = new Map();
|
||||||
|
this.obj = obj;
|
||||||
|
}
|
||||||
|
return Mirror;
|
||||||
|
}());
|
||||||
|
var ObserverInfo = /** @class */ (function () {
|
||||||
|
function ObserverInfo(callback, observer) {
|
||||||
|
this.callback = callback;
|
||||||
|
this.observer = observer;
|
||||||
|
}
|
||||||
|
return ObserverInfo;
|
||||||
|
}());
|
||||||
|
function getMirror(obj) {
|
||||||
|
return beforeDict.get(obj);
|
||||||
|
}
|
||||||
|
function getObserverFromMirror(mirror, callback) {
|
||||||
|
return mirror.observers.get(callback);
|
||||||
|
}
|
||||||
|
function removeObserverFromMirror(mirror, observer) {
|
||||||
|
mirror.observers.delete(observer.callback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Detach an observer from an object
|
||||||
|
*/
|
||||||
|
function unobserve(root, observer) {
|
||||||
|
observer.unobserve();
|
||||||
|
}
|
||||||
|
exports.unobserve = unobserve;
|
||||||
|
/**
|
||||||
|
* Observes changes made to an object, which can then be retrieved using generate
|
||||||
|
*/
|
||||||
|
function observe(obj, callback) {
|
||||||
|
var patches = [];
|
||||||
|
var observer;
|
||||||
|
var mirror = getMirror(obj);
|
||||||
|
if (!mirror) {
|
||||||
|
mirror = new Mirror(obj);
|
||||||
|
beforeDict.set(obj, mirror);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var observerInfo = getObserverFromMirror(mirror, callback);
|
||||||
|
observer = observerInfo && observerInfo.observer;
|
||||||
|
}
|
||||||
|
if (observer) {
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
|
observer = {};
|
||||||
|
mirror.value = helpers_js_1._deepClone(obj);
|
||||||
|
if (callback) {
|
||||||
|
observer.callback = callback;
|
||||||
|
observer.next = null;
|
||||||
|
var dirtyCheck = function () {
|
||||||
|
generate(observer);
|
||||||
|
};
|
||||||
|
var fastCheck = function () {
|
||||||
|
clearTimeout(observer.next);
|
||||||
|
observer.next = setTimeout(dirtyCheck);
|
||||||
|
};
|
||||||
|
if (typeof window !== 'undefined') { //not Node
|
||||||
|
window.addEventListener('mouseup', fastCheck);
|
||||||
|
window.addEventListener('keyup', fastCheck);
|
||||||
|
window.addEventListener('mousedown', fastCheck);
|
||||||
|
window.addEventListener('keydown', fastCheck);
|
||||||
|
window.addEventListener('change', fastCheck);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observer.patches = patches;
|
||||||
|
observer.object = obj;
|
||||||
|
observer.unobserve = function () {
|
||||||
|
generate(observer);
|
||||||
|
clearTimeout(observer.next);
|
||||||
|
removeObserverFromMirror(mirror, observer);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('mouseup', fastCheck);
|
||||||
|
window.removeEventListener('keyup', fastCheck);
|
||||||
|
window.removeEventListener('mousedown', fastCheck);
|
||||||
|
window.removeEventListener('keydown', fastCheck);
|
||||||
|
window.removeEventListener('change', fastCheck);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mirror.observers.set(callback, new ObserverInfo(callback, observer));
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
|
exports.observe = observe;
|
||||||
|
/**
|
||||||
|
* Generate an array of patches from an observer
|
||||||
|
*/
|
||||||
|
function generate(observer, invertible) {
|
||||||
|
if (invertible === void 0) { invertible = false; }
|
||||||
|
var mirror = beforeDict.get(observer.object);
|
||||||
|
_generate(mirror.value, observer.object, observer.patches, "", invertible);
|
||||||
|
if (observer.patches.length) {
|
||||||
|
core_js_1.applyPatch(mirror.value, observer.patches);
|
||||||
|
}
|
||||||
|
var temp = observer.patches;
|
||||||
|
if (temp.length > 0) {
|
||||||
|
observer.patches = [];
|
||||||
|
if (observer.callback) {
|
||||||
|
observer.callback(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
exports.generate = generate;
|
||||||
|
// Dirty check if obj is different from mirror, generate patches and update mirror
|
||||||
|
function _generate(mirror, obj, patches, path, invertible) {
|
||||||
|
if (obj === mirror) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof obj.toJSON === "function") {
|
||||||
|
obj = obj.toJSON();
|
||||||
|
}
|
||||||
|
var newKeys = helpers_js_1._objectKeys(obj);
|
||||||
|
var oldKeys = helpers_js_1._objectKeys(mirror);
|
||||||
|
var changed = false;
|
||||||
|
var deleted = false;
|
||||||
|
//if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)"
|
||||||
|
for (var t = oldKeys.length - 1; t >= 0; t--) {
|
||||||
|
var key = oldKeys[t];
|
||||||
|
var oldVal = mirror[key];
|
||||||
|
if (helpers_js_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) {
|
||||||
|
var newVal = obj[key];
|
||||||
|
if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null && Array.isArray(oldVal) === Array.isArray(newVal)) {
|
||||||
|
_generate(oldVal, newVal, patches, path + "/" + helpers_js_1.escapePathComponent(key), invertible);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (oldVal !== newVal) {
|
||||||
|
changed = true;
|
||||||
|
if (invertible) {
|
||||||
|
patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) });
|
||||||
|
}
|
||||||
|
patches.push({ op: "replace", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(newVal) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Array.isArray(mirror) === Array.isArray(obj)) {
|
||||||
|
if (invertible) {
|
||||||
|
patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) });
|
||||||
|
}
|
||||||
|
patches.push({ op: "remove", path: path + "/" + helpers_js_1.escapePathComponent(key) });
|
||||||
|
deleted = true; // property has been deleted
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (invertible) {
|
||||||
|
patches.push({ op: "test", path: path, value: mirror });
|
||||||
|
}
|
||||||
|
patches.push({ op: "replace", path: path, value: obj });
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!deleted && newKeys.length == oldKeys.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var t = 0; t < newKeys.length; t++) {
|
||||||
|
var key = newKeys[t];
|
||||||
|
if (!helpers_js_1.hasOwnProperty(mirror, key) && obj[key] !== undefined) {
|
||||||
|
patches.push({ op: "add", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(obj[key]) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create an array of patches from the differences in two objects
|
||||||
|
*/
|
||||||
|
function compare(tree1, tree2, invertible) {
|
||||||
|
if (invertible === void 0) { invertible = false; }
|
||||||
|
var patches = [];
|
||||||
|
_generate(tree1, tree2, patches, '', invertible);
|
||||||
|
return patches;
|
||||||
|
}
|
||||||
|
exports.compare = compare;
|
||||||
|
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
/******/ ]);
|
14
app/static/external/JSON-Patch/js/fast-json-patch.min.js
vendored
Normal file
14
app/static/external/JSON-Patch/js/fast-json-patch.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
app/static/external/list.js/LICENSE
vendored
Normal file
21
app/static/external/list.js/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2011-2018 Jonny Strömberg, jonnystromberg.com
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
1
app/static/external/list.js/VERSION
vendored
Normal file
1
app/static/external/list.js/VERSION
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
2.3.1
|
2020
app/static/external/list.js/js/list.js
vendored
Normal file
2020
app/static/external/list.js/js/list.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
app/static/external/list.js/js/list.js.map
vendored
Normal file
1
app/static/external/list.js/js/list.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
app/static/external/list.js/js/list.min.js
vendored
Normal file
2
app/static/external/list.js/js/list.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
app/static/external/list.js/js/list.min.js.map
vendored
Normal file
1
app/static/external/list.js/js/list.min.js.map
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"list.min.js","sources":["webpack://List/list.min.js"],"mappings":"AAAA","sourceRoot":""}
|
202
app/static/external/material-design-icons/LICENSE
vendored
Normal file
202
app/static/external/material-design-icons/LICENSE
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
1
app/static/external/material-design-icons/VERSION
vendored
Normal file
1
app/static/external/material-design-icons/VERSION
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
https://github.com/google/material-design-icons/tree/f7bd4f25f3764883717c09a1fd867f560c9a9581
|
37
app/static/external/material-design-icons/css/material-icons.css
vendored
Normal file
37
app/static/external/material-design-icons/css/material-icons.css
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Material Icons";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
/* For IE6-8 */
|
||||||
|
/* src: url("../font/MaterialIcons-Regular.eot"); */
|
||||||
|
src: local("Material Icons"),
|
||||||
|
local("MaterialIcons-Regular"),
|
||||||
|
/* url("../font/MaterialIcons-Regular.woff2") format('woff2'), */
|
||||||
|
/* url("../font/MaterialIcons-Regular.woff") format('woff'), */
|
||||||
|
url("../font/MaterialIcons-Regular.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-icons {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px; /* Preferred icon size */
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
|
/* Support for Firefox. */
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
/* Support for IE. */
|
||||||
|
font-feature-settings: 'liga';
|
||||||
|
}
|
2235
app/static/external/material-design-icons/font/MaterialIcons-Regular.codepoints
vendored
Normal file
2235
app/static/external/material-design-icons/font/MaterialIcons-Regular.codepoints
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/static/external/material-design-icons/font/MaterialIcons-Regular.ttf
vendored
Normal file
BIN
app/static/external/material-design-icons/font/MaterialIcons-Regular.ttf
vendored
Normal file
Binary file not shown.
2195
app/static/external/material-design-icons/font/MaterialIconsOutlined-Regular.codepoints
vendored
Normal file
2195
app/static/external/material-design-icons/font/MaterialIconsOutlined-Regular.codepoints
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/static/external/material-design-icons/font/MaterialIconsOutlined-Regular.otf
vendored
Normal file
BIN
app/static/external/material-design-icons/font/MaterialIconsOutlined-Regular.otf
vendored
Normal file
Binary file not shown.
2200
app/static/external/material-design-icons/font/MaterialIconsRound-Regular.codepoints
vendored
Normal file
2200
app/static/external/material-design-icons/font/MaterialIconsRound-Regular.codepoints
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/static/external/material-design-icons/font/MaterialIconsRound-Regular.otf
vendored
Normal file
BIN
app/static/external/material-design-icons/font/MaterialIconsRound-Regular.otf
vendored
Normal file
Binary file not shown.
2200
app/static/external/material-design-icons/font/MaterialIconsSharp-Regular.codepoints
vendored
Normal file
2200
app/static/external/material-design-icons/font/MaterialIconsSharp-Regular.codepoints
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/static/external/material-design-icons/font/MaterialIconsSharp-Regular.otf
vendored
Normal file
BIN
app/static/external/material-design-icons/font/MaterialIconsSharp-Regular.otf
vendored
Normal file
Binary file not shown.
2238
app/static/external/material-design-icons/font/MaterialIconsTwoTone-Regular.codepoints
vendored
Normal file
2238
app/static/external/material-design-icons/font/MaterialIconsTwoTone-Regular.codepoints
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
app/static/external/material-design-icons/font/MaterialIconsTwoTone-Regular.otf
vendored
Normal file
BIN
app/static/external/material-design-icons/font/MaterialIconsTwoTone-Regular.otf
vendored
Normal file
Binary file not shown.
21
app/static/external/materialize/LICENSE
vendored
Normal file
21
app/static/external/materialize/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2018 Materialize
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
1
app/static/external/materialize/VERSION
vendored
Normal file
1
app/static/external/materialize/VERSION
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
1.0.0
|
9067
app/static/external/materialize/css/materialize.css
vendored
Normal file
9067
app/static/external/materialize/css/materialize.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
13
app/static/external/materialize/css/materialize.min.css
vendored
Normal file
13
app/static/external/materialize/css/materialize.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12374
app/static/external/materialize/js/materialize.js
vendored
Normal file
12374
app/static/external/materialize/js/materialize.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
app/static/external/materialize/js/materialize.min.js
vendored
Normal file
6
app/static/external/materialize/js/materialize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
55
app/static/external/materialize/sass/components/_badges.scss
vendored
Normal file
55
app/static/external/materialize/sass/components/_badges.scss
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Badges
|
||||||
|
span.badge {
|
||||||
|
min-width: 3rem;
|
||||||
|
padding: 0 6px;
|
||||||
|
margin-left: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: $badge-height;
|
||||||
|
height: $badge-height;
|
||||||
|
color: color('grey', 'darken-1');
|
||||||
|
float: right;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&.new {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
|
background-color: $badge-bg-color;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
&.new:after {
|
||||||
|
content: " new";
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-badge-caption]::after {
|
||||||
|
content: " " attr(data-badge-caption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special cases
|
||||||
|
nav ul a span.badge {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
margin-left: 4px;
|
||||||
|
line-height: $badge-height;
|
||||||
|
height: $badge-height;
|
||||||
|
-webkit-font-smoothing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line height centering
|
||||||
|
.collection-item span.badge {
|
||||||
|
margin-top: calc(#{$collection-line-height / 2} - #{$badge-height / 2});
|
||||||
|
}
|
||||||
|
.collapsible span.badge {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.sidenav span.badge {
|
||||||
|
margin-top: calc(#{$sidenav-line-height / 2} - #{$badge-height / 2});
|
||||||
|
}
|
||||||
|
|
||||||
|
table span.badge {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
322
app/static/external/materialize/sass/components/_buttons.scss
vendored
Normal file
322
app/static/external/materialize/sass/components/_buttons.scss
vendored
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
// shared styles
|
||||||
|
.btn,
|
||||||
|
.btn-flat {
|
||||||
|
border: $button-border;
|
||||||
|
border-radius: $button-radius;
|
||||||
|
display: inline-block;
|
||||||
|
height: $button-height;
|
||||||
|
line-height: $button-height;
|
||||||
|
padding: $button-padding;
|
||||||
|
text-transform: uppercase;
|
||||||
|
vertical-align: middle;
|
||||||
|
-webkit-tap-highlight-color: transparent; // Gets rid of tap active state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled shared style
|
||||||
|
.btn.disabled,
|
||||||
|
.btn-floating.disabled,
|
||||||
|
.btn-large.disabled,
|
||||||
|
.btn-small.disabled,
|
||||||
|
.btn-flat.disabled,
|
||||||
|
.btn:disabled,
|
||||||
|
.btn-floating:disabled,
|
||||||
|
.btn-large:disabled,
|
||||||
|
.btn-small:disabled,
|
||||||
|
.btn-flat:disabled,
|
||||||
|
.btn[disabled],
|
||||||
|
.btn-floating[disabled],
|
||||||
|
.btn-large[disabled],
|
||||||
|
.btn-small[disabled],
|
||||||
|
.btn-flat[disabled] {
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: $button-disabled-background !important;
|
||||||
|
box-shadow: none;
|
||||||
|
color: $button-disabled-color !important;
|
||||||
|
cursor: default;
|
||||||
|
&:hover {
|
||||||
|
background-color: $button-disabled-background !important;
|
||||||
|
color: $button-disabled-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared icon styles
|
||||||
|
.btn,
|
||||||
|
.btn-floating,
|
||||||
|
.btn-large,
|
||||||
|
.btn-small,
|
||||||
|
.btn-flat {
|
||||||
|
font-size: $button-font-size;
|
||||||
|
outline: 0;
|
||||||
|
i {
|
||||||
|
font-size: $button-icon-font-size;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared focus button style
|
||||||
|
.btn,
|
||||||
|
.btn-floating {
|
||||||
|
&:focus {
|
||||||
|
background-color: darken($button-raised-background, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raised Button
|
||||||
|
.btn {
|
||||||
|
text-decoration: none;
|
||||||
|
color: $button-raised-color;
|
||||||
|
background-color: $button-raised-background;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
@extend .z-depth-1;
|
||||||
|
transition: background-color .2s ease-out;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: $button-raised-background-hover;
|
||||||
|
@extend .z-depth-1-half;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floating button
|
||||||
|
.btn-floating {
|
||||||
|
&:hover {
|
||||||
|
background-color: $button-floating-background-hover;
|
||||||
|
@extend .z-depth-1-half;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
&.btn-large {
|
||||||
|
&.halfway-fab {
|
||||||
|
bottom: -$button-floating-large-size / 2;
|
||||||
|
}
|
||||||
|
width: $button-floating-large-size;
|
||||||
|
height: $button-floating-large-size;
|
||||||
|
padding: 0;
|
||||||
|
i {
|
||||||
|
line-height: $button-floating-large-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-small {
|
||||||
|
&.halfway-fab {
|
||||||
|
bottom: -$button-floating-small-size / 2;
|
||||||
|
}
|
||||||
|
width: $button-floating-small-size;
|
||||||
|
height: $button-floating-small-size;
|
||||||
|
i {
|
||||||
|
line-height: $button-floating-small-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.halfway-fab {
|
||||||
|
&.left {
|
||||||
|
right: auto;
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
bottom: -$button-floating-size / 2;
|
||||||
|
}
|
||||||
|
display: inline-block;
|
||||||
|
color: $button-floating-color;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
width: $button-floating-size;
|
||||||
|
height: $button-floating-size;
|
||||||
|
line-height: $button-floating-size;
|
||||||
|
padding: 0;
|
||||||
|
background-color: $button-floating-background;
|
||||||
|
border-radius: $button-floating-radius;
|
||||||
|
@extend .z-depth-1;
|
||||||
|
transition: background-color .3s;
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
i {
|
||||||
|
width: inherit;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
color: $button-floating-color;
|
||||||
|
font-size: $button-large-icon-font-size;
|
||||||
|
line-height: $button-floating-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// button fix
|
||||||
|
button.btn-floating {
|
||||||
|
border: $button-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed Action Button
|
||||||
|
.fixed-action-btn {
|
||||||
|
&.active {
|
||||||
|
ul {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directions
|
||||||
|
&.direction-left,
|
||||||
|
&.direction-right {
|
||||||
|
padding: 0 0 0 15px;
|
||||||
|
ul {
|
||||||
|
text-align: right;
|
||||||
|
right: 64px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 100%;
|
||||||
|
left: auto;
|
||||||
|
/*width 100% only goes to width of button container */
|
||||||
|
width: 500px;
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 7.5px 15px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.direction-right {
|
||||||
|
padding: 0 15px 0 0;
|
||||||
|
ul {
|
||||||
|
text-align: left;
|
||||||
|
direction: rtl;
|
||||||
|
left: 64px;
|
||||||
|
right: auto;
|
||||||
|
li {
|
||||||
|
margin: 7.5px 0 0 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.direction-bottom {
|
||||||
|
padding: 0 0 15px 0;
|
||||||
|
ul {
|
||||||
|
top: 64px;
|
||||||
|
bottom: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
li {
|
||||||
|
margin: 15px 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.toolbar {
|
||||||
|
&.active {
|
||||||
|
&>a i {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
padding: 0;
|
||||||
|
height: $button-floating-large-size;
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
li {
|
||||||
|
flex: 1;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
transition: none;
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #fff;
|
||||||
|
line-height: $button-floating-large-size;
|
||||||
|
z-index: 1;
|
||||||
|
i {
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position: fixed;
|
||||||
|
right: 23px;
|
||||||
|
bottom: 23px;
|
||||||
|
padding-top: 15px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
z-index: 997;
|
||||||
|
ul {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 64px;
|
||||||
|
margin: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
li {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
a.btn-floating {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fab-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: $button-floating-size;
|
||||||
|
height: $button-floating-size;
|
||||||
|
background-color: $button-floating-background;
|
||||||
|
border-radius: $button-floating-radius;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flat button
|
||||||
|
.btn-flat {
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: $button-flat-color;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color .2s;
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background-color: rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
&.disabled,
|
||||||
|
&.btn-flat[disabled] {
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: $button-flat-disabled-color !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large button
|
||||||
|
.btn-large {
|
||||||
|
@extend .btn;
|
||||||
|
height: $button-large-height;
|
||||||
|
line-height: $button-large-height;
|
||||||
|
font-size: $button-large-font-size;
|
||||||
|
padding: 0 28px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: $button-large-icon-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small button
|
||||||
|
.btn-small {
|
||||||
|
@extend .btn;
|
||||||
|
height: $button-small-height;
|
||||||
|
line-height: $button-small-height;
|
||||||
|
font-size: $button-small-font-size;
|
||||||
|
i {
|
||||||
|
font-size: $button-small-icon-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block button
|
||||||
|
.btn-block {
|
||||||
|
display: block;
|
||||||
|
}
|
195
app/static/external/materialize/sass/components/_cards.scss
vendored
Normal file
195
app/static/external/materialize/sass/components/_cards.scss
vendored
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.card-panel {
|
||||||
|
transition: box-shadow .25s;
|
||||||
|
padding: $card-padding;
|
||||||
|
margin: $element-top-margin 0 $element-bottom-margin 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
@extend .z-depth-1;
|
||||||
|
background-color: $card-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
margin: $element-top-margin 0 $element-bottom-margin 0;
|
||||||
|
background-color: $card-bg-color;
|
||||||
|
transition: box-shadow .25s;
|
||||||
|
border-radius: 2px;
|
||||||
|
@extend .z-depth-1;
|
||||||
|
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 300;
|
||||||
|
&.activator {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card Sizes
|
||||||
|
&.small, &.medium, &.large {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
max-height: 60%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.card-image + .card-content {
|
||||||
|
max-height: 40%;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.card-action {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal Cards
|
||||||
|
&.horizontal {
|
||||||
|
&.small, &.medium, &.large {
|
||||||
|
.card-image {
|
||||||
|
height: 100%;
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
max-width: 50%;
|
||||||
|
img {
|
||||||
|
border-radius: 2px 0 0 2px;
|
||||||
|
max-width: 100%;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stacked {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky Action Section
|
||||||
|
&.sticky-action {
|
||||||
|
.card-action {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-reveal {
|
||||||
|
z-index: 1;
|
||||||
|
padding-bottom: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
// Image background for content
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
position: relative;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: $card-bg-color;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: $card-padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: $card-padding;
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.card-title {
|
||||||
|
display: block;
|
||||||
|
line-height: 32px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-action {
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
}
|
||||||
|
background-color: inherit; // Use inherit to inherit color classes
|
||||||
|
border-top: 1px solid rgba(160,160,160,.2);
|
||||||
|
position: relative;
|
||||||
|
padding: 16px $card-padding;
|
||||||
|
|
||||||
|
a:not(.btn):not(.btn-large):not(.btn-floating) {
|
||||||
|
color: $card-link-color;
|
||||||
|
margin-right: $card-padding;
|
||||||
|
transition: color .3s ease;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&:hover { color: $card-link-color-light; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-reveal {
|
||||||
|
padding: $card-padding;
|
||||||
|
position: absolute;
|
||||||
|
background-color: $card-bg-color;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
left: 0;
|
||||||
|
top: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
app/static/external/materialize/sass/components/_carousel.scss
vendored
Normal file
90
app/static/external/materialize/sass/components/_carousel.scss
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
.carousel {
|
||||||
|
&.carousel-slider {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
.carousel-fixed-item {
|
||||||
|
&.with-indicators {
|
||||||
|
bottom: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 20px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: $carousel-height;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: $carousel-height;
|
||||||
|
perspective: 500px;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transform-origin: 0% 50%;
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
visibility: hidden;
|
||||||
|
width: $carousel-item-width;
|
||||||
|
height: $carousel-item-height;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
& > img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicators {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.indicator-item {
|
||||||
|
&.active {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
margin: 24px 4px;
|
||||||
|
background-color: rgba(255,255,255,.5);
|
||||||
|
|
||||||
|
transition: background-color .3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Materialbox compatibility
|
||||||
|
&.scrolling .carousel-item .materialboxed,
|
||||||
|
.carousel-item:not(.active) .materialboxed {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
90
app/static/external/materialize/sass/components/_chips.scss
vendored
Normal file
90
app/static/external/materialize/sass/components/_chips.scss
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
.chip {
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
background-color: $chip-selected-color;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(0,0,0,.6);
|
||||||
|
line-height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: $chip-bg-color;
|
||||||
|
margin-bottom: $chip-margin;
|
||||||
|
margin-right: $chip-margin;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
float: left;
|
||||||
|
margin: 0 8px 0 -12px;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 32px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chips {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid $chip-border-color;
|
||||||
|
box-shadow: none;
|
||||||
|
margin: $input-margin;
|
||||||
|
min-height: 45px;
|
||||||
|
outline: none;
|
||||||
|
transition: all .3s;
|
||||||
|
|
||||||
|
&.focus {
|
||||||
|
border-bottom: 1px solid $chip-selected-color;
|
||||||
|
box-shadow: 0 1px 0 0 $chip-selected-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
color: rgba(0,0,0,.6);
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $input-font-size;
|
||||||
|
height: $input-height;
|
||||||
|
line-height: 32px;
|
||||||
|
outline: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 120px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
border: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autocomplete
|
||||||
|
.autocomplete-content {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form prefix
|
||||||
|
.prefix ~ .chips {
|
||||||
|
margin-left: 3rem;
|
||||||
|
width: 92%;
|
||||||
|
width: calc(100% - 3rem);
|
||||||
|
}
|
||||||
|
.chips:empty ~ label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
transform: translateY(-140%);
|
||||||
|
}
|
91
app/static/external/materialize/sass/components/_collapsible.scss
vendored
Normal file
91
app/static/external/materialize/sass/components/_collapsible.scss
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
.collapsible {
|
||||||
|
border-top: 1px solid $collapsible-border-color;
|
||||||
|
border-right: 1px solid $collapsible-border-color;
|
||||||
|
border-left: 1px solid $collapsible-border-color;
|
||||||
|
margin: $element-top-margin 0 $element-bottom-margin 0;
|
||||||
|
@extend .z-depth-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-header {
|
||||||
|
&:focus {
|
||||||
|
outline: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: $collapsible-header-color;
|
||||||
|
border-bottom: 1px solid $collapsible-border-color;
|
||||||
|
|
||||||
|
i {
|
||||||
|
width: 2rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.keyboard-focused .collapsible-header:focus {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-body {
|
||||||
|
display: none;
|
||||||
|
border-bottom: 1px solid $collapsible-border-color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sidenav collapsible styling
|
||||||
|
.sidenav,
|
||||||
|
.sidenav.fixed {
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
li { padding: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-header {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
line-height: inherit;
|
||||||
|
height: inherit;
|
||||||
|
padding: 0 $sidenav-padding;
|
||||||
|
|
||||||
|
&:hover { background-color: rgba(0,0,0,.05); }
|
||||||
|
i { line-height: inherit; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-body {
|
||||||
|
border: 0;
|
||||||
|
background-color: $collapsible-header-color;
|
||||||
|
|
||||||
|
li a {
|
||||||
|
padding: 0 (7.5px + $sidenav-padding)
|
||||||
|
0 (15px + $sidenav-padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popout Collapsible
|
||||||
|
|
||||||
|
.collapsible.popout {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
> li {
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
// transform: scaleX(.92);
|
||||||
|
margin: 0 24px;
|
||||||
|
transition: margin .35s cubic-bezier(0.250, 0.460, 0.450, 0.940);
|
||||||
|
}
|
||||||
|
> li.active {
|
||||||
|
box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15);
|
||||||
|
margin: 16px 0;
|
||||||
|
// transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
32
app/static/external/materialize/sass/components/_color-classes.scss
vendored
Normal file
32
app/static/external/materialize/sass/components/_color-classes.scss
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Color Classes
|
||||||
|
|
||||||
|
@each $color_name, $color in $colors {
|
||||||
|
@each $color_type, $color_value in $color {
|
||||||
|
@if $color_type == "base" {
|
||||||
|
.#{$color_name} {
|
||||||
|
background-color: $color_value !important;
|
||||||
|
}
|
||||||
|
.#{$color_name}-text {
|
||||||
|
color: $color_value !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@else if $color_name != "shades" {
|
||||||
|
.#{$color_name}.#{$color_type} {
|
||||||
|
background-color: $color_value !important;
|
||||||
|
}
|
||||||
|
.#{$color_name}-text.text-#{$color_type} {
|
||||||
|
color: $color_value !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shade classes
|
||||||
|
@each $color, $color_value in $shades {
|
||||||
|
.#{$color} {
|
||||||
|
background-color: $color_value !important;
|
||||||
|
}
|
||||||
|
.#{$color}-text {
|
||||||
|
color: $color_value !important;
|
||||||
|
}
|
||||||
|
}
|
370
app/static/external/materialize/sass/components/_color-variables.scss
vendored
Normal file
370
app/static/external/materialize/sass/components/_color-variables.scss
vendored
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
// Google Color Palette defined: http://www.google.com/design/spec/style/color.html
|
||||||
|
|
||||||
|
$materialize-red: (
|
||||||
|
"base": #e51c23,
|
||||||
|
"lighten-5": #fdeaeb,
|
||||||
|
"lighten-4": #f8c1c3,
|
||||||
|
"lighten-3": #f3989b,
|
||||||
|
"lighten-2": #ee6e73,
|
||||||
|
"lighten-1": #ea454b,
|
||||||
|
"darken-1": #d0181e,
|
||||||
|
"darken-2": #b9151b,
|
||||||
|
"darken-3": #a21318,
|
||||||
|
"darken-4": #8b1014,
|
||||||
|
);
|
||||||
|
|
||||||
|
$red: (
|
||||||
|
"base": #F44336,
|
||||||
|
"lighten-5": #FFEBEE,
|
||||||
|
"lighten-4": #FFCDD2,
|
||||||
|
"lighten-3": #EF9A9A,
|
||||||
|
"lighten-2": #E57373,
|
||||||
|
"lighten-1": #EF5350,
|
||||||
|
"darken-1": #E53935,
|
||||||
|
"darken-2": #D32F2F,
|
||||||
|
"darken-3": #C62828,
|
||||||
|
"darken-4": #B71C1C,
|
||||||
|
"accent-1": #FF8A80,
|
||||||
|
"accent-2": #FF5252,
|
||||||
|
"accent-3": #FF1744,
|
||||||
|
"accent-4": #D50000
|
||||||
|
);
|
||||||
|
|
||||||
|
$pink: (
|
||||||
|
"base": #e91e63,
|
||||||
|
"lighten-5": #fce4ec,
|
||||||
|
"lighten-4": #f8bbd0,
|
||||||
|
"lighten-3": #f48fb1,
|
||||||
|
"lighten-2": #f06292,
|
||||||
|
"lighten-1": #ec407a,
|
||||||
|
"darken-1": #d81b60,
|
||||||
|
"darken-2": #c2185b,
|
||||||
|
"darken-3": #ad1457,
|
||||||
|
"darken-4": #880e4f,
|
||||||
|
"accent-1": #ff80ab,
|
||||||
|
"accent-2": #ff4081,
|
||||||
|
"accent-3": #f50057,
|
||||||
|
"accent-4": #c51162
|
||||||
|
);
|
||||||
|
|
||||||
|
$purple: (
|
||||||
|
"base": #9c27b0,
|
||||||
|
"lighten-5": #f3e5f5,
|
||||||
|
"lighten-4": #e1bee7,
|
||||||
|
"lighten-3": #ce93d8,
|
||||||
|
"lighten-2": #ba68c8,
|
||||||
|
"lighten-1": #ab47bc,
|
||||||
|
"darken-1": #8e24aa,
|
||||||
|
"darken-2": #7b1fa2,
|
||||||
|
"darken-3": #6a1b9a,
|
||||||
|
"darken-4": #4a148c,
|
||||||
|
"accent-1": #ea80fc,
|
||||||
|
"accent-2": #e040fb,
|
||||||
|
"accent-3": #d500f9,
|
||||||
|
"accent-4": #aa00ff
|
||||||
|
);
|
||||||
|
|
||||||
|
$deep-purple: (
|
||||||
|
"base": #673ab7,
|
||||||
|
"lighten-5": #ede7f6,
|
||||||
|
"lighten-4": #d1c4e9,
|
||||||
|
"lighten-3": #b39ddb,
|
||||||
|
"lighten-2": #9575cd,
|
||||||
|
"lighten-1": #7e57c2,
|
||||||
|
"darken-1": #5e35b1,
|
||||||
|
"darken-2": #512da8,
|
||||||
|
"darken-3": #4527a0,
|
||||||
|
"darken-4": #311b92,
|
||||||
|
"accent-1": #b388ff,
|
||||||
|
"accent-2": #7c4dff,
|
||||||
|
"accent-3": #651fff,
|
||||||
|
"accent-4": #6200ea
|
||||||
|
);
|
||||||
|
|
||||||
|
$indigo: (
|
||||||
|
"base": #3f51b5,
|
||||||
|
"lighten-5": #e8eaf6,
|
||||||
|
"lighten-4": #c5cae9,
|
||||||
|
"lighten-3": #9fa8da,
|
||||||
|
"lighten-2": #7986cb,
|
||||||
|
"lighten-1": #5c6bc0,
|
||||||
|
"darken-1": #3949ab,
|
||||||
|
"darken-2": #303f9f,
|
||||||
|
"darken-3": #283593,
|
||||||
|
"darken-4": #1a237e,
|
||||||
|
"accent-1": #8c9eff,
|
||||||
|
"accent-2": #536dfe,
|
||||||
|
"accent-3": #3d5afe,
|
||||||
|
"accent-4": #304ffe
|
||||||
|
);
|
||||||
|
|
||||||
|
$blue: (
|
||||||
|
"base": #2196F3,
|
||||||
|
"lighten-5": #E3F2FD,
|
||||||
|
"lighten-4": #BBDEFB,
|
||||||
|
"lighten-3": #90CAF9,
|
||||||
|
"lighten-2": #64B5F6,
|
||||||
|
"lighten-1": #42A5F5,
|
||||||
|
"darken-1": #1E88E5,
|
||||||
|
"darken-2": #1976D2,
|
||||||
|
"darken-3": #1565C0,
|
||||||
|
"darken-4": #0D47A1,
|
||||||
|
"accent-1": #82B1FF,
|
||||||
|
"accent-2": #448AFF,
|
||||||
|
"accent-3": #2979FF,
|
||||||
|
"accent-4": #2962FF
|
||||||
|
);
|
||||||
|
|
||||||
|
$light-blue: (
|
||||||
|
"base": #03a9f4,
|
||||||
|
"lighten-5": #e1f5fe,
|
||||||
|
"lighten-4": #b3e5fc,
|
||||||
|
"lighten-3": #81d4fa,
|
||||||
|
"lighten-2": #4fc3f7,
|
||||||
|
"lighten-1": #29b6f6,
|
||||||
|
"darken-1": #039be5,
|
||||||
|
"darken-2": #0288d1,
|
||||||
|
"darken-3": #0277bd,
|
||||||
|
"darken-4": #01579b,
|
||||||
|
"accent-1": #80d8ff,
|
||||||
|
"accent-2": #40c4ff,
|
||||||
|
"accent-3": #00b0ff,
|
||||||
|
"accent-4": #0091ea
|
||||||
|
);
|
||||||
|
|
||||||
|
$cyan: (
|
||||||
|
"base": #00bcd4,
|
||||||
|
"lighten-5": #e0f7fa,
|
||||||
|
"lighten-4": #b2ebf2,
|
||||||
|
"lighten-3": #80deea,
|
||||||
|
"lighten-2": #4dd0e1,
|
||||||
|
"lighten-1": #26c6da,
|
||||||
|
"darken-1": #00acc1,
|
||||||
|
"darken-2": #0097a7,
|
||||||
|
"darken-3": #00838f,
|
||||||
|
"darken-4": #006064,
|
||||||
|
"accent-1": #84ffff,
|
||||||
|
"accent-2": #18ffff,
|
||||||
|
"accent-3": #00e5ff,
|
||||||
|
"accent-4": #00b8d4
|
||||||
|
);
|
||||||
|
|
||||||
|
$teal: (
|
||||||
|
"base": #009688,
|
||||||
|
"lighten-5": #e0f2f1,
|
||||||
|
"lighten-4": #b2dfdb,
|
||||||
|
"lighten-3": #80cbc4,
|
||||||
|
"lighten-2": #4db6ac,
|
||||||
|
"lighten-1": #26a69a,
|
||||||
|
"darken-1": #00897b,
|
||||||
|
"darken-2": #00796b,
|
||||||
|
"darken-3": #00695c,
|
||||||
|
"darken-4": #004d40,
|
||||||
|
"accent-1": #a7ffeb,
|
||||||
|
"accent-2": #64ffda,
|
||||||
|
"accent-3": #1de9b6,
|
||||||
|
"accent-4": #00bfa5
|
||||||
|
);
|
||||||
|
|
||||||
|
$green: (
|
||||||
|
"base": #4CAF50,
|
||||||
|
"lighten-5": #E8F5E9,
|
||||||
|
"lighten-4": #C8E6C9,
|
||||||
|
"lighten-3": #A5D6A7,
|
||||||
|
"lighten-2": #81C784,
|
||||||
|
"lighten-1": #66BB6A,
|
||||||
|
"darken-1": #43A047,
|
||||||
|
"darken-2": #388E3C,
|
||||||
|
"darken-3": #2E7D32,
|
||||||
|
"darken-4": #1B5E20,
|
||||||
|
"accent-1": #B9F6CA,
|
||||||
|
"accent-2": #69F0AE,
|
||||||
|
"accent-3": #00E676,
|
||||||
|
"accent-4": #00C853
|
||||||
|
);
|
||||||
|
|
||||||
|
$light-green: (
|
||||||
|
"base": #8bc34a,
|
||||||
|
"lighten-5": #f1f8e9,
|
||||||
|
"lighten-4": #dcedc8,
|
||||||
|
"lighten-3": #c5e1a5,
|
||||||
|
"lighten-2": #aed581,
|
||||||
|
"lighten-1": #9ccc65,
|
||||||
|
"darken-1": #7cb342,
|
||||||
|
"darken-2": #689f38,
|
||||||
|
"darken-3": #558b2f,
|
||||||
|
"darken-4": #33691e,
|
||||||
|
"accent-1": #ccff90,
|
||||||
|
"accent-2": #b2ff59,
|
||||||
|
"accent-3": #76ff03,
|
||||||
|
"accent-4": #64dd17
|
||||||
|
);
|
||||||
|
|
||||||
|
$lime: (
|
||||||
|
"base": #cddc39,
|
||||||
|
"lighten-5": #f9fbe7,
|
||||||
|
"lighten-4": #f0f4c3,
|
||||||
|
"lighten-3": #e6ee9c,
|
||||||
|
"lighten-2": #dce775,
|
||||||
|
"lighten-1": #d4e157,
|
||||||
|
"darken-1": #c0ca33,
|
||||||
|
"darken-2": #afb42b,
|
||||||
|
"darken-3": #9e9d24,
|
||||||
|
"darken-4": #827717,
|
||||||
|
"accent-1": #f4ff81,
|
||||||
|
"accent-2": #eeff41,
|
||||||
|
"accent-3": #c6ff00,
|
||||||
|
"accent-4": #aeea00
|
||||||
|
);
|
||||||
|
|
||||||
|
$yellow: (
|
||||||
|
"base": #ffeb3b,
|
||||||
|
"lighten-5": #fffde7,
|
||||||
|
"lighten-4": #fff9c4,
|
||||||
|
"lighten-3": #fff59d,
|
||||||
|
"lighten-2": #fff176,
|
||||||
|
"lighten-1": #ffee58,
|
||||||
|
"darken-1": #fdd835,
|
||||||
|
"darken-2": #fbc02d,
|
||||||
|
"darken-3": #f9a825,
|
||||||
|
"darken-4": #f57f17,
|
||||||
|
"accent-1": #ffff8d,
|
||||||
|
"accent-2": #ffff00,
|
||||||
|
"accent-3": #ffea00,
|
||||||
|
"accent-4": #ffd600
|
||||||
|
);
|
||||||
|
|
||||||
|
$amber: (
|
||||||
|
"base": #ffc107,
|
||||||
|
"lighten-5": #fff8e1,
|
||||||
|
"lighten-4": #ffecb3,
|
||||||
|
"lighten-3": #ffe082,
|
||||||
|
"lighten-2": #ffd54f,
|
||||||
|
"lighten-1": #ffca28,
|
||||||
|
"darken-1": #ffb300,
|
||||||
|
"darken-2": #ffa000,
|
||||||
|
"darken-3": #ff8f00,
|
||||||
|
"darken-4": #ff6f00,
|
||||||
|
"accent-1": #ffe57f,
|
||||||
|
"accent-2": #ffd740,
|
||||||
|
"accent-3": #ffc400,
|
||||||
|
"accent-4": #ffab00
|
||||||
|
);
|
||||||
|
|
||||||
|
$orange: (
|
||||||
|
"base": #ff9800,
|
||||||
|
"lighten-5": #fff3e0,
|
||||||
|
"lighten-4": #ffe0b2,
|
||||||
|
"lighten-3": #ffcc80,
|
||||||
|
"lighten-2": #ffb74d,
|
||||||
|
"lighten-1": #ffa726,
|
||||||
|
"darken-1": #fb8c00,
|
||||||
|
"darken-2": #f57c00,
|
||||||
|
"darken-3": #ef6c00,
|
||||||
|
"darken-4": #e65100,
|
||||||
|
"accent-1": #ffd180,
|
||||||
|
"accent-2": #ffab40,
|
||||||
|
"accent-3": #ff9100,
|
||||||
|
"accent-4": #ff6d00
|
||||||
|
);
|
||||||
|
|
||||||
|
$deep-orange: (
|
||||||
|
"base": #ff5722,
|
||||||
|
"lighten-5": #fbe9e7,
|
||||||
|
"lighten-4": #ffccbc,
|
||||||
|
"lighten-3": #ffab91,
|
||||||
|
"lighten-2": #ff8a65,
|
||||||
|
"lighten-1": #ff7043,
|
||||||
|
"darken-1": #f4511e,
|
||||||
|
"darken-2": #e64a19,
|
||||||
|
"darken-3": #d84315,
|
||||||
|
"darken-4": #bf360c,
|
||||||
|
"accent-1": #ff9e80,
|
||||||
|
"accent-2": #ff6e40,
|
||||||
|
"accent-3": #ff3d00,
|
||||||
|
"accent-4": #dd2c00
|
||||||
|
);
|
||||||
|
|
||||||
|
$brown: (
|
||||||
|
"base": #795548,
|
||||||
|
"lighten-5": #efebe9,
|
||||||
|
"lighten-4": #d7ccc8,
|
||||||
|
"lighten-3": #bcaaa4,
|
||||||
|
"lighten-2": #a1887f,
|
||||||
|
"lighten-1": #8d6e63,
|
||||||
|
"darken-1": #6d4c41,
|
||||||
|
"darken-2": #5d4037,
|
||||||
|
"darken-3": #4e342e,
|
||||||
|
"darken-4": #3e2723
|
||||||
|
);
|
||||||
|
|
||||||
|
$blue-grey: (
|
||||||
|
"base": #607d8b,
|
||||||
|
"lighten-5": #eceff1,
|
||||||
|
"lighten-4": #cfd8dc,
|
||||||
|
"lighten-3": #b0bec5,
|
||||||
|
"lighten-2": #90a4ae,
|
||||||
|
"lighten-1": #78909c,
|
||||||
|
"darken-1": #546e7a,
|
||||||
|
"darken-2": #455a64,
|
||||||
|
"darken-3": #37474f,
|
||||||
|
"darken-4": #263238
|
||||||
|
);
|
||||||
|
|
||||||
|
$grey: (
|
||||||
|
"base": #9e9e9e,
|
||||||
|
"lighten-5": #fafafa,
|
||||||
|
"lighten-4": #f5f5f5,
|
||||||
|
"lighten-3": #eeeeee,
|
||||||
|
"lighten-2": #e0e0e0,
|
||||||
|
"lighten-1": #bdbdbd,
|
||||||
|
"darken-1": #757575,
|
||||||
|
"darken-2": #616161,
|
||||||
|
"darken-3": #424242,
|
||||||
|
"darken-4": #212121
|
||||||
|
);
|
||||||
|
|
||||||
|
$shades: (
|
||||||
|
"black": #000000,
|
||||||
|
"white": #FFFFFF,
|
||||||
|
"transparent": transparent
|
||||||
|
);
|
||||||
|
|
||||||
|
$colors: (
|
||||||
|
"materialize-red": $materialize-red,
|
||||||
|
"red": $red,
|
||||||
|
"pink": $pink,
|
||||||
|
"purple": $purple,
|
||||||
|
"deep-purple": $deep-purple,
|
||||||
|
"indigo": $indigo,
|
||||||
|
"blue": $blue,
|
||||||
|
"light-blue": $light-blue,
|
||||||
|
"cyan": $cyan,
|
||||||
|
"teal": $teal,
|
||||||
|
"green": $green,
|
||||||
|
"light-green": $light-green,
|
||||||
|
"lime": $lime,
|
||||||
|
"yellow": $yellow,
|
||||||
|
"amber": $amber,
|
||||||
|
"orange": $orange,
|
||||||
|
"deep-orange": $deep-orange,
|
||||||
|
"brown": $brown,
|
||||||
|
"blue-grey": $blue-grey,
|
||||||
|
"grey": $grey,
|
||||||
|
"shades": $shades
|
||||||
|
) !default;
|
||||||
|
|
||||||
|
|
||||||
|
// usage: color("name_of_color", "type_of_color")
|
||||||
|
// to avoid to repeating map-get($colors, ...)
|
||||||
|
|
||||||
|
@function color($color, $type) {
|
||||||
|
@if map-has-key($colors, $color) {
|
||||||
|
$curr_color: map-get($colors, $color);
|
||||||
|
@if map-has-key($curr_color, $type) {
|
||||||
|
@return map-get($curr_color, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@warn "Unknown `#{$color}` - `#{$type}` in $colors.";
|
||||||
|
@return null;
|
||||||
|
}
|
191
app/static/external/materialize/sass/components/_datepicker.scss
vendored
Normal file
191
app/static/external/materialize/sass/components/_datepicker.scss
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
/* Modal */
|
||||||
|
.datepicker-modal {
|
||||||
|
max-width: 325px;
|
||||||
|
min-width: 300px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-container.modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.selects-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
input {
|
||||||
|
&:focus {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
border-bottom: none;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-year input {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-month input {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-prev, .month-next {
|
||||||
|
margin-top: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Date Display */
|
||||||
|
.datepicker-date-display {
|
||||||
|
flex: 1 auto;
|
||||||
|
background-color: $secondary-color;
|
||||||
|
color: #fff;
|
||||||
|
padding: 20px 22px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.year-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 25px;
|
||||||
|
color: $datepicker-year;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 2.8rem;
|
||||||
|
line-height: 47px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Calendar */
|
||||||
|
.datepicker-calendar-container {
|
||||||
|
flex: 2.5 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-table {
|
||||||
|
width: 280px;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
thead {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 10px 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr {
|
||||||
|
text-decoration: none;
|
||||||
|
color: $datepicker-calendar-header-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
&.is-today {
|
||||||
|
color: $secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-selected {
|
||||||
|
background-color: $secondary-color;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-outside-current-month,
|
||||||
|
&.is-disabled {
|
||||||
|
color: $datepicker-disabled-day-color;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day-button {
|
||||||
|
&:focus {
|
||||||
|
background-color: $datepicker-day-focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
line-height: 38px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.datepicker-footer {
|
||||||
|
width: 280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-cancel,
|
||||||
|
.datepicker-clear,
|
||||||
|
.datepicker-today,
|
||||||
|
.datepicker-done {
|
||||||
|
color: $secondary-color;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-clear {
|
||||||
|
color: $error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Media Queries */
|
||||||
|
@media #{$medium-and-up} {
|
||||||
|
.datepicker-modal {
|
||||||
|
max-width: 625px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-container.modal-content {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-date-display {
|
||||||
|
flex: 0 1 270px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-controls,
|
||||||
|
.datepicker-table,
|
||||||
|
.datepicker-footer {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day-button {
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
85
app/static/external/materialize/sass/components/_dropdown.scss
vendored
Normal file
85
app/static/external/materialize/sass/components/_dropdown.scss
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
.dropdown-content {
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@extend .z-depth-1;
|
||||||
|
background-color: $dropdown-bg-color;
|
||||||
|
margin: 0;
|
||||||
|
display: none;
|
||||||
|
min-width: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 9999; // TODO: Check if this doesn't break other things
|
||||||
|
transform-origin: 0 0;
|
||||||
|
|
||||||
|
|
||||||
|
li {
|
||||||
|
&:hover, &.active {
|
||||||
|
background-color: $dropdown-hover-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.divider {
|
||||||
|
min-height: 0;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a, & > span {
|
||||||
|
font-size: 16px;
|
||||||
|
color: $dropdown-color;
|
||||||
|
display: block;
|
||||||
|
line-height: 22px;
|
||||||
|
padding: (($dropdown-item-height - 22) / 2) 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span > label {
|
||||||
|
top: 1px;
|
||||||
|
left: 0;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon alignment override
|
||||||
|
& > a > i {
|
||||||
|
height: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
float: left;
|
||||||
|
margin: 0 24px 0 0;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
clear: both;
|
||||||
|
color: $off-black;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: $dropdown-item-height;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.keyboard-focused {
|
||||||
|
.dropdown-content li:focus {
|
||||||
|
background-color: darken($dropdown-hover-bg-color, 8%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input field specificity bugfix
|
||||||
|
.input-field.col .dropdown-content [type="checkbox"] + label {
|
||||||
|
top: 1px;
|
||||||
|
left: 0;
|
||||||
|
height: 18px;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
769
app/static/external/materialize/sass/components/_global.scss
vendored
Normal file
769
app/static/external/materialize/sass/components/_global.scss
vendored
Normal file
@ -0,0 +1,769 @@
|
|||||||
|
//Default styles
|
||||||
|
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
// display: flex;
|
||||||
|
// min-height: 100vh;
|
||||||
|
// flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
// flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: $font-stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
&:not(.browser-default) {
|
||||||
|
padding-left: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
& > li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $link-color;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
// Gets rid of tap active state
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Positioning
|
||||||
|
.valign-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// classic clearfix
|
||||||
|
.clearfix {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Z-levels
|
||||||
|
.z-depth-0 {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2dp elevation modified*/
|
||||||
|
.z-depth-1 {
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14),
|
||||||
|
0 3px 1px -2px rgba(0,0,0,0.12),
|
||||||
|
0 1px 5px 0 rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.z-depth-1-half {
|
||||||
|
box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6dp elevation modified*/
|
||||||
|
.z-depth-2 {
|
||||||
|
box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14),
|
||||||
|
0 1px 10px 0 rgba(0,0,0,0.12),
|
||||||
|
0 2px 4px -1px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 12dp elevation modified*/
|
||||||
|
.z-depth-3 {
|
||||||
|
box-shadow: 0 8px 17px 2px rgba(0,0,0,0.14),
|
||||||
|
0 3px 14px 2px rgba(0,0,0,0.12),
|
||||||
|
0 5px 5px -3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 16dp elevation */
|
||||||
|
.z-depth-4 {
|
||||||
|
box-shadow: 0 16px 24px 2px rgba(0,0,0,0.14),
|
||||||
|
0 6px 30px 5px rgba(0,0,0,0.12),
|
||||||
|
0 8px 10px -7px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 24dp elevation */
|
||||||
|
.z-depth-5 {
|
||||||
|
box-shadow: 0 24px 38px 3px rgba(0,0,0,0.14),
|
||||||
|
0 9px 46px 8px rgba(0,0,0,0.12),
|
||||||
|
0 11px 15px -7px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverable {
|
||||||
|
transition: box-shadow .25s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dividers
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: color("grey", "lighten-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Blockquote
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
border-left: 5px solid $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon Styles
|
||||||
|
|
||||||
|
i {
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
float: left;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
&.right {
|
||||||
|
float: right;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
&.tiny {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
&.small {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
&.medium {
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
&.large {
|
||||||
|
font-size: 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
|
img.responsive-img,
|
||||||
|
video.responsive-video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 2px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: top;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #444;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 0 10px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active a { color: #fff; }
|
||||||
|
|
||||||
|
&.active { background-color: $primary-color; }
|
||||||
|
|
||||||
|
&.disabled a {
|
||||||
|
cursor: default;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
li.pages ul li {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
.pagination {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
li.prev,
|
||||||
|
li.next {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.pages {
|
||||||
|
width: 80%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
.breadcrumb {
|
||||||
|
font-size: 18px;
|
||||||
|
color: rgba(255,255,255, .7);
|
||||||
|
|
||||||
|
i,
|
||||||
|
[class^="mdi-"], [class*="mdi-"],
|
||||||
|
i.material-icons {
|
||||||
|
display: inline-block;
|
||||||
|
float: left;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '\E5CC';
|
||||||
|
color: rgba(255,255,255, .7);
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 25px;
|
||||||
|
margin: 0 10px 0 8px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallax
|
||||||
|
.parallax-container {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 500px;
|
||||||
|
|
||||||
|
.parallax {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
img {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pushpin
|
||||||
|
.pin-top, .pin-bottom {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.pinned {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
Transition Classes
|
||||||
|
**********************/
|
||||||
|
|
||||||
|
ul.staggered-list li {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
Media Query Classes
|
||||||
|
**********************/
|
||||||
|
.hide-on-small-only, .hide-on-small-and-down {
|
||||||
|
@media #{$small-and-down} {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hide-on-med-and-down {
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hide-on-med-and-up {
|
||||||
|
@media #{$medium-and-up} {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hide-on-med-only {
|
||||||
|
@media only screen and (min-width: $small-screen) and (max-width: $medium-screen) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hide-on-large-only {
|
||||||
|
@media #{$large-and-up} {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hide-on-extra-large-only {
|
||||||
|
@media #{$extra-large-and-up} {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show-on-extra-large {
|
||||||
|
@media #{$extra-large-and-up} {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show-on-large {
|
||||||
|
@media #{$large-and-up} {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show-on-medium {
|
||||||
|
@media only screen and (min-width: $small-screen) and (max-width: $medium-screen) {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show-on-small {
|
||||||
|
@media #{$small-and-down} {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show-on-medium-and-up {
|
||||||
|
@media #{$medium-and-up} {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show-on-medium-and-down {
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Center text on mobile
|
||||||
|
.center-on-small-only {
|
||||||
|
@media #{$small-and-down} {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
.page-footer {
|
||||||
|
padding-top: 20px;
|
||||||
|
color: $footer-font-color;
|
||||||
|
background-color: $footer-bg-color;
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 0px;
|
||||||
|
color: $footer-copyright-font-color;
|
||||||
|
background-color: $footer-copyright-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
table, th, td {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width:100%;
|
||||||
|
display: table;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
|
||||||
|
&.striped {
|
||||||
|
tr {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> tbody {
|
||||||
|
> tr:nth-child(odd) {
|
||||||
|
background-color: $table-striped-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
> tr > td {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.highlight > tbody > tr {
|
||||||
|
transition: background-color .25s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: $table-striped-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.centered {
|
||||||
|
thead tr th, tbody tr td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border-bottom: 1px solid $table-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th{
|
||||||
|
padding: 15px 5px;
|
||||||
|
display: table-cell;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive Table
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
|
||||||
|
table.responsive-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
td:empty:before {
|
||||||
|
content: '\00a0';
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
th { text-align: left; }
|
||||||
|
thead {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
display: block;
|
||||||
|
padding: 0 10px 0 0;
|
||||||
|
|
||||||
|
th::before {
|
||||||
|
content: "\00a0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
display: block;
|
||||||
|
min-height: 1.25em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sort out borders */
|
||||||
|
thead {
|
||||||
|
border: 0;
|
||||||
|
border-right: 1px solid $table-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Collections
|
||||||
|
.collection {
|
||||||
|
margin: $element-top-margin 0 $element-bottom-margin 0;
|
||||||
|
border: 1px solid $collection-border-color;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.collection-item {
|
||||||
|
background-color: $collection-bg-color;
|
||||||
|
line-height: $collection-line-height;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid $collection-border-color;
|
||||||
|
|
||||||
|
// Avatar Collection
|
||||||
|
&.avatar {
|
||||||
|
min-height: 84px;
|
||||||
|
padding-left: 72px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
// Don't style circles inside preloader classes.
|
||||||
|
&:not(.circle-clipper) > .circle,
|
||||||
|
:not(.circle-clipper) > .circle {
|
||||||
|
position: absolute;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 15px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
i.circle {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 42px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $collection-active-bg-color;
|
||||||
|
color: $collection-active-color;
|
||||||
|
|
||||||
|
.secondary-content {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.collection-item{
|
||||||
|
display: block;
|
||||||
|
transition: .25s;
|
||||||
|
color: $collection-link-color;
|
||||||
|
&:not(.active) {
|
||||||
|
&:hover {
|
||||||
|
background-color: $collection-hover-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.with-header {
|
||||||
|
.collection-header {
|
||||||
|
background-color: $collection-bg-color;
|
||||||
|
border-bottom: 1px solid $collection-border-color;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
.collection-item {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
.collection-item.avatar {
|
||||||
|
padding-left: 72px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// Made less specific to allow easier overriding
|
||||||
|
.secondary-content {
|
||||||
|
float: right;
|
||||||
|
color: $secondary-color;
|
||||||
|
}
|
||||||
|
.collapsible .collection {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Responsive Videos
|
||||||
|
.video-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
iframe, object, embed {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress Bar
|
||||||
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
height: 4px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background-color: lighten($progress-bar-color, 40%);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: $element-top-margin 0 $element-bottom-margin 0;
|
||||||
|
overflow: hidden;
|
||||||
|
.determinate {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: $progress-bar-color;
|
||||||
|
transition: width .3s linear;
|
||||||
|
}
|
||||||
|
.indeterminate {
|
||||||
|
background-color: $progress-bar-color;
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: inherit;
|
||||||
|
top: 0;
|
||||||
|
left:0;
|
||||||
|
bottom: 0;
|
||||||
|
will-change: left, right;
|
||||||
|
// Custom bezier
|
||||||
|
animation: indeterminate 2.1s cubic-bezier(0.650, 0.815, 0.735, 0.395) infinite;
|
||||||
|
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: inherit;
|
||||||
|
top: 0;
|
||||||
|
left:0;
|
||||||
|
bottom: 0;
|
||||||
|
will-change: left, right;
|
||||||
|
// Custom bezier
|
||||||
|
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.840, 0.440, 1.000) infinite;
|
||||||
|
animation-delay: 1.15s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes indeterminate {
|
||||||
|
0% {
|
||||||
|
left: -35%;
|
||||||
|
right:100%;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
left: 100%;
|
||||||
|
right: -90%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
right: -90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes indeterminate-short {
|
||||||
|
0% {
|
||||||
|
left: -200%;
|
||||||
|
right: 100%;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
left: 107%;
|
||||||
|
right: -8%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 107%;
|
||||||
|
right: -8%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************
|
||||||
|
Utility Classes
|
||||||
|
*******************/
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text Align
|
||||||
|
.left-align {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.right-align {
|
||||||
|
text-align: right
|
||||||
|
}
|
||||||
|
.center, .center-align {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float: left !important;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
float: right !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Text Select
|
||||||
|
.no-select {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-block {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-padding {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
156
app/static/external/materialize/sass/components/_grid.scss
vendored
Normal file
156
app/static/external/materialize/sass/components/_grid.scss
vendored
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
.container {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1280px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
@media #{$medium-and-up} {
|
||||||
|
.container {
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media #{$large-and-up} {
|
||||||
|
.container {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.col .row {
|
||||||
|
margin-left: (-1 * $gutter-width / 2);
|
||||||
|
margin-right: (-1 * $gutter-width / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
|
||||||
|
&.no-pad {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
&.no-pad-bot {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
&.no-pad-top {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Mixins to eliminate code repitition
|
||||||
|
@mixin reset-offset {
|
||||||
|
margin-left: auto;
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
@mixin grid-classes($size, $i, $perc) {
|
||||||
|
&.offset-#{$size}#{$i} {
|
||||||
|
margin-left: $perc;
|
||||||
|
}
|
||||||
|
&.pull-#{$size}#{$i} {
|
||||||
|
right: $perc;
|
||||||
|
}
|
||||||
|
&.push-#{$size}#{$i} {
|
||||||
|
left: $perc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
// Clear floating children
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
float: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 $gutter-width / 2;
|
||||||
|
min-height: 1px;
|
||||||
|
|
||||||
|
&[class*="push-"],
|
||||||
|
&[class*="pull-"] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
&.s#{$i} {
|
||||||
|
width: $perc;
|
||||||
|
@include reset-offset;
|
||||||
|
}
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
@include grid-classes("s", $i, $perc);
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$medium-and-up} {
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
&.m#{$i} {
|
||||||
|
width: $perc;
|
||||||
|
@include reset-offset;
|
||||||
|
}
|
||||||
|
$i: $i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
@include grid-classes("m", $i, $perc);
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$large-and-up} {
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
&.l#{$i} {
|
||||||
|
width: $perc;
|
||||||
|
@include reset-offset;
|
||||||
|
}
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
@include grid-classes("l", $i, $perc);
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$extra-large-and-up} {
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
&.xl#{$i} {
|
||||||
|
width: $perc;
|
||||||
|
@include reset-offset;
|
||||||
|
}
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i: 1;
|
||||||
|
@while $i <= $num-cols {
|
||||||
|
$perc: unquote((100 / ($num-cols / $i)) + "%");
|
||||||
|
@include grid-classes("xl", $i, $perc);
|
||||||
|
$i: $i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
app/static/external/materialize/sass/components/_icons-material-design.scss
vendored
Normal file
5
app/static/external/materialize/sass/components/_icons-material-design.scss
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/* This is needed for some mobile phones to display the Google Icon font properly */
|
||||||
|
.material-icons {
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
font-feature-settings: 'liga';
|
||||||
|
}
|
43
app/static/external/materialize/sass/components/_materialbox.scss
vendored
Normal file
43
app/static/external/materialize/sass/components/_materialbox.scss
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.materialboxed {
|
||||||
|
&:hover {
|
||||||
|
&:not(.active) {
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
cursor: zoom-in;
|
||||||
|
position: relative;
|
||||||
|
transition: opacity .4s;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
cursor: zoom-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#materialbox-overlay {
|
||||||
|
position:fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #292929;
|
||||||
|
z-index: 1000;
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
.materialbox-caption {
|
||||||
|
position: fixed;
|
||||||
|
display: none;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 50px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0% 15%;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 1000;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
94
app/static/external/materialize/sass/components/_modal.scss
vendored
Normal file
94
app/static/external/materialize/sass/components/_modal.scss
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
.modal {
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@extend .z-depth-5;
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 70%;
|
||||||
|
width: 55%;
|
||||||
|
margin: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
will-change: top, opacity;
|
||||||
|
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,h2,h3,h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
.modal-close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 4px 6px;
|
||||||
|
height: 56px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.btn, .btn-flat {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999;
|
||||||
|
top: -25%;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 125%;
|
||||||
|
width: 100%;
|
||||||
|
background: #000;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
will-change: opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal with fixed action footer
|
||||||
|
.modal.modal-fixed-footer {
|
||||||
|
padding: 0;
|
||||||
|
height: 70%;
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: absolute;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
max-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-top: 1px solid rgba(0,0,0,.1);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal Bottom Sheet Style
|
||||||
|
.modal.bottom-sheet {
|
||||||
|
top: auto;
|
||||||
|
bottom: -100%;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 45%;
|
||||||
|
border-radius: 0;
|
||||||
|
will-change: bottom, opacity;
|
||||||
|
}
|
208
app/static/external/materialize/sass/components/_navbar.scss
vendored
Normal file
208
app/static/external/materialize/sass/components/_navbar.scss
vendored
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
nav {
|
||||||
|
&.nav-extended {
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.nav-wrapper {
|
||||||
|
min-height: $navbar-height-mobile;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-content {
|
||||||
|
position: relative;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color: $navbar-font-color;
|
||||||
|
@extend .z-depth-1;
|
||||||
|
background-color: $primary-color;
|
||||||
|
width: 100%;
|
||||||
|
height: $navbar-height-mobile;
|
||||||
|
line-height: $navbar-line-height-mobile;
|
||||||
|
|
||||||
|
a { color: $navbar-font-color; }
|
||||||
|
|
||||||
|
i,
|
||||||
|
[class^="mdi-"], [class*="mdi-"],
|
||||||
|
i.material-icons {
|
||||||
|
display: block;
|
||||||
|
font-size: 24px;
|
||||||
|
height: $navbar-height-mobile;
|
||||||
|
line-height: $navbar-line-height-mobile;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$large-and-up} {
|
||||||
|
a.sidenav-trigger { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Collapse button
|
||||||
|
.sidenav-trigger {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
height: $navbar-height-mobile;
|
||||||
|
margin: 0 18px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
height: $navbar-height-mobile;
|
||||||
|
line-height: $navbar-line-height-mobile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Logo
|
||||||
|
.brand-logo {
|
||||||
|
position: absolute;
|
||||||
|
color: $navbar-font-color;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $navbar-brand-font-size;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
&.left, &.right {
|
||||||
|
padding: 0;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left { left: 0.5rem; }
|
||||||
|
&.right {
|
||||||
|
right: 0.5rem;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
right: 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i,
|
||||||
|
[class^="mdi-"], [class*="mdi-"],
|
||||||
|
i.material-icons {
|
||||||
|
float: left;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Title
|
||||||
|
.nav-title {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 32px;
|
||||||
|
padding: 28px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Navbar Links
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
transition: background-color .3s;
|
||||||
|
float: left;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: rgba(0,0,0,.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
transition: background-color .3s;
|
||||||
|
font-size: $navbar-font-size;
|
||||||
|
color: $navbar-font-color;
|
||||||
|
display: block;
|
||||||
|
padding: 0 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.btn, &.btn-large, &.btn-flat, &.btn-floating {
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
|
& > .material-icons {
|
||||||
|
height: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0,0,0,.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar Search Form
|
||||||
|
form {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
height: 100%;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
border: none;
|
||||||
|
padding-left: 2rem;
|
||||||
|
|
||||||
|
&:focus, &[type=text]:valid, &[type=password]:valid,
|
||||||
|
&[type=email]:valid, &[type=url]:valid, &[type=date]:valid {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: rgba(255,255,255,.7);
|
||||||
|
transition: color .3s;
|
||||||
|
}
|
||||||
|
&.active i { color: $navbar-font-color; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed Navbar
|
||||||
|
.navbar-fixed {
|
||||||
|
position: relative;
|
||||||
|
height: $navbar-height-mobile;
|
||||||
|
z-index: 997;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media #{$medium-and-up} {
|
||||||
|
nav.nav-extended .nav-wrapper {
|
||||||
|
min-height: $navbar-height;
|
||||||
|
}
|
||||||
|
nav, nav .nav-wrapper i, nav a.sidenav-trigger, nav a.sidenav-trigger i {
|
||||||
|
height: $navbar-height;
|
||||||
|
line-height: $navbar-line-height;
|
||||||
|
}
|
||||||
|
.navbar-fixed {
|
||||||
|
height: $navbar-height;
|
||||||
|
}
|
||||||
|
}
|
447
app/static/external/materialize/sass/components/_normalize.scss
vendored
Normal file
447
app/static/external/materialize/sass/components/_normalize.scss
vendored
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||||
|
|
||||||
|
/* Document
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the line height in all browsers.
|
||||||
|
* 2. Prevent adjustments of font size after orientation changes in
|
||||||
|
* IE on Windows Phone and in iOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
-ms-text-size-adjust: 100%; /* 2 */
|
||||||
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the margin in all browsers (opinionated).
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 9-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the font size and margin on `h1` elements within `section` and
|
||||||
|
* `article` contexts in Chrome, Firefox, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouping content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 9-.
|
||||||
|
* 1. Add the correct display in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
main { /* 1 */
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct margin in IE 8.
|
||||||
|
*/
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 1em 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in Firefox.
|
||||||
|
* 2. Show the overflow in Edge and IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box; /* 1 */
|
||||||
|
height: 0; /* 1 */
|
||||||
|
overflow: visible; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace; /* 1 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text-level semantics
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Remove the gray background on active links in IE 10.
|
||||||
|
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent; /* 1 */
|
||||||
|
-webkit-text-decoration-skip: objects; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
|
||||||
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none; /* 1 */
|
||||||
|
text-decoration: underline; /* 2 */
|
||||||
|
text-decoration: underline dotted; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace; /* 1 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font style in Android 4.3-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dfn {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct background and color in IE 9-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background-color: #ff0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||||
|
* all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Embedded content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 9-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
audio,
|
||||||
|
video {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in iOS 4-7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
audio:not([controls]) {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the border on images inside links in IE 10-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the overflow in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Change the font styles in all browsers (opinionated).
|
||||||
|
* 2. Remove the margin in Firefox and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: sans-serif; /* 1 */
|
||||||
|
font-size: 100%; /* 1 */
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
margin: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the overflow in IE.
|
||||||
|
* 1. Show the overflow in Edge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input { /* 1 */
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||||
|
* 1. Remove the inheritance of text transform in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select { /* 1 */
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||||
|
* controls in Android 4.
|
||||||
|
* 2. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
html [type="button"], /* 1 */
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner border and padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the focus styles unset by the previous rule.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button:-moz-focusring,
|
||||||
|
[type="button"]:-moz-focusring,
|
||||||
|
[type="reset"]:-moz-focusring,
|
||||||
|
[type="submit"]:-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding: 0.35em 0.75em 0.625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the text wrapping in Edge and IE.
|
||||||
|
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||||
|
* 3. Remove the padding so developers are not caught out when they zero out
|
||||||
|
* `fieldset` elements in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
legend {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
color: inherit; /* 2 */
|
||||||
|
display: table; /* 1 */
|
||||||
|
max-width: 100%; /* 1 */
|
||||||
|
padding: 0; /* 3 */
|
||||||
|
white-space: normal; /* 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct display in IE 9-.
|
||||||
|
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
display: inline-block; /* 1 */
|
||||||
|
vertical-align: baseline; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the default vertical scrollbar in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in IE 10-.
|
||||||
|
* 2. Remove the padding in IE 10-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="checkbox"],
|
||||||
|
[type="radio"] {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
padding: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
* 2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield; /* 1 */
|
||||||
|
outline-offset: -2px; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-cancel-button,
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
* 2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button; /* 1 */
|
||||||
|
font: inherit; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in IE 9-.
|
||||||
|
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
details, /* 1 */
|
||||||
|
menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scripting
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 9-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hidden
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10-.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
334
app/static/external/materialize/sass/components/_preloader.scss
vendored
Normal file
334
app/static/external/materialize/sass/components/_preloader.scss
vendored
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
/*
|
||||||
|
@license
|
||||||
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**************************/
|
||||||
|
/* STYLES FOR THE SPINNER */
|
||||||
|
/**************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constants:
|
||||||
|
* STROKEWIDTH = 3px
|
||||||
|
* ARCSIZE = 270 degrees (amount of circle the arc takes up)
|
||||||
|
* ARCTIME = 1333ms (time it takes to expand and contract arc)
|
||||||
|
* ARCSTARTROT = 216 degrees (how much the start location of the arc
|
||||||
|
* should rotate each time, 216 gives us a
|
||||||
|
* 5 pointed star shape (it's 360/5 * 3).
|
||||||
|
* For a 7 pointed star, we might do
|
||||||
|
* 360/7 * 3 = 154.286)
|
||||||
|
* CONTAINERWIDTH = 28px
|
||||||
|
* SHRINK_TIME = 400ms
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
.preloader-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.big {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
/* duration: 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */
|
||||||
|
-webkit-animation: container-rotate 1568ms linear infinite;
|
||||||
|
animation: container-rotate 1568ms linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes container-rotate {
|
||||||
|
to { -webkit-transform: rotate(360deg) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes container-rotate {
|
||||||
|
to { transform: rotate(360deg) }
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-layer {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
border-color: $spinner-default-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-blue,
|
||||||
|
.spinner-blue-only {
|
||||||
|
border-color: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-red,
|
||||||
|
.spinner-red-only {
|
||||||
|
border-color: #db4437;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-yellow,
|
||||||
|
.spinner-yellow-only {
|
||||||
|
border-color: #f4b400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-green,
|
||||||
|
.spinner-green-only {
|
||||||
|
border-color: #0f9d58;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT NOTE ABOUT CSS ANIMATION PROPERTIES (keanulee):
|
||||||
|
*
|
||||||
|
* iOS Safari (tested on iOS 8.1) does not handle animation-delay very well - it doesn't
|
||||||
|
* guarantee that the animation will start _exactly_ after that value. So we avoid using
|
||||||
|
* animation-delay and instead set custom keyframes for each color (as redundant as it
|
||||||
|
* seems).
|
||||||
|
*
|
||||||
|
* We write out each animation in full (instead of separating animation-name,
|
||||||
|
* animation-duration, etc.) because under the polyfill, Safari does not recognize those
|
||||||
|
* specific properties properly, treats them as -webkit-animation, and overrides the
|
||||||
|
* other animation rules. See https://github.com/Polymer/platform/issues/53.
|
||||||
|
*/
|
||||||
|
.active .spinner-layer.spinner-blue {
|
||||||
|
/* durations: 4 * ARCTIME */
|
||||||
|
-webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, blue-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active .spinner-layer.spinner-red {
|
||||||
|
/* durations: 4 * ARCTIME */
|
||||||
|
-webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, red-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active .spinner-layer.spinner-yellow {
|
||||||
|
/* durations: 4 * ARCTIME */
|
||||||
|
-webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, yellow-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active .spinner-layer.spinner-green {
|
||||||
|
/* durations: 4 * ARCTIME */
|
||||||
|
-webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, green-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active .spinner-layer,
|
||||||
|
.active .spinner-layer.spinner-blue-only,
|
||||||
|
.active .spinner-layer.spinner-red-only,
|
||||||
|
.active .spinner-layer.spinner-yellow-only,
|
||||||
|
.active .spinner-layer.spinner-green-only {
|
||||||
|
/* durations: 4 * ARCTIME */
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fill-unfill-rotate {
|
||||||
|
12.5% { -webkit-transform: rotate(135deg); } /* 0.5 * ARCSIZE */
|
||||||
|
25% { -webkit-transform: rotate(270deg); } /* 1 * ARCSIZE */
|
||||||
|
37.5% { -webkit-transform: rotate(405deg); } /* 1.5 * ARCSIZE */
|
||||||
|
50% { -webkit-transform: rotate(540deg); } /* 2 * ARCSIZE */
|
||||||
|
62.5% { -webkit-transform: rotate(675deg); } /* 2.5 * ARCSIZE */
|
||||||
|
75% { -webkit-transform: rotate(810deg); } /* 3 * ARCSIZE */
|
||||||
|
87.5% { -webkit-transform: rotate(945deg); } /* 3.5 * ARCSIZE */
|
||||||
|
to { -webkit-transform: rotate(1080deg); } /* 4 * ARCSIZE */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fill-unfill-rotate {
|
||||||
|
12.5% { transform: rotate(135deg); } /* 0.5 * ARCSIZE */
|
||||||
|
25% { transform: rotate(270deg); } /* 1 * ARCSIZE */
|
||||||
|
37.5% { transform: rotate(405deg); } /* 1.5 * ARCSIZE */
|
||||||
|
50% { transform: rotate(540deg); } /* 2 * ARCSIZE */
|
||||||
|
62.5% { transform: rotate(675deg); } /* 2.5 * ARCSIZE */
|
||||||
|
75% { transform: rotate(810deg); } /* 3 * ARCSIZE */
|
||||||
|
87.5% { transform: rotate(945deg); } /* 3.5 * ARCSIZE */
|
||||||
|
to { transform: rotate(1080deg); } /* 4 * ARCSIZE */
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes blue-fade-in-out {
|
||||||
|
from { opacity: 1; }
|
||||||
|
25% { opacity: 1; }
|
||||||
|
26% { opacity: 0; }
|
||||||
|
89% { opacity: 0; }
|
||||||
|
90% { opacity: 1; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blue-fade-in-out {
|
||||||
|
from { opacity: 1; }
|
||||||
|
25% { opacity: 1; }
|
||||||
|
26% { opacity: 0; }
|
||||||
|
89% { opacity: 0; }
|
||||||
|
90% { opacity: 1; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes red-fade-in-out {
|
||||||
|
from { opacity: 0; }
|
||||||
|
15% { opacity: 0; }
|
||||||
|
25% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
51% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes red-fade-in-out {
|
||||||
|
from { opacity: 0; }
|
||||||
|
15% { opacity: 0; }
|
||||||
|
25% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
51% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes yellow-fade-in-out {
|
||||||
|
from { opacity: 0; }
|
||||||
|
40% { opacity: 0; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
75% { opacity: 1; }
|
||||||
|
76% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes yellow-fade-in-out {
|
||||||
|
from { opacity: 0; }
|
||||||
|
40% { opacity: 0; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
75% { opacity: 1; }
|
||||||
|
76% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes green-fade-in-out {
|
||||||
|
from { opacity: 0; }
|
||||||
|
65% { opacity: 0; }
|
||||||
|
75% { opacity: 1; }
|
||||||
|
90% { opacity: 1; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes green-fade-in-out {
|
||||||
|
from { opacity: 0; }
|
||||||
|
65% { opacity: 0; }
|
||||||
|
75% { opacity: 1; }
|
||||||
|
90% { opacity: 1; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch the gap that appear between the two adjacent div.circle-clipper while the
|
||||||
|
* spinner is rotating (appears on Chrome 38, Safari 7.1, and IE 11).
|
||||||
|
*/
|
||||||
|
.gap-patch {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 45%;
|
||||||
|
width: 10%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-patch .circle {
|
||||||
|
width: 1000%;
|
||||||
|
left: -450%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-clipper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: inherit;
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 200%;
|
||||||
|
height: 100%;
|
||||||
|
border-width: 3px; /* STROKEWIDTH */
|
||||||
|
border-style: solid;
|
||||||
|
border-color: inherit;
|
||||||
|
border-bottom-color: transparent !important;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-animation: none;
|
||||||
|
animation: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left .circle {
|
||||||
|
left: 0;
|
||||||
|
border-right-color: transparent !important;
|
||||||
|
-webkit-transform: rotate(129deg);
|
||||||
|
transform: rotate(129deg);
|
||||||
|
}
|
||||||
|
&.right .circle {
|
||||||
|
left: -100%;
|
||||||
|
border-left-color: transparent !important;
|
||||||
|
-webkit-transform: rotate(-129deg);
|
||||||
|
transform: rotate(-129deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.active .circle-clipper.left .circle {
|
||||||
|
/* duration: ARCTIME */
|
||||||
|
-webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active .circle-clipper.right .circle {
|
||||||
|
/* duration: ARCTIME */
|
||||||
|
-webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes left-spin {
|
||||||
|
from { -webkit-transform: rotate(130deg); }
|
||||||
|
50% { -webkit-transform: rotate(-5deg); }
|
||||||
|
to { -webkit-transform: rotate(130deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes left-spin {
|
||||||
|
from { transform: rotate(130deg); }
|
||||||
|
50% { transform: rotate(-5deg); }
|
||||||
|
to { transform: rotate(130deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes right-spin {
|
||||||
|
from { -webkit-transform: rotate(-130deg); }
|
||||||
|
50% { -webkit-transform: rotate(5deg); }
|
||||||
|
to { -webkit-transform: rotate(-130deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes right-spin {
|
||||||
|
from { transform: rotate(-130deg); }
|
||||||
|
50% { transform: rotate(5deg); }
|
||||||
|
to { transform: rotate(-130deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#spinnerContainer.cooldown {
|
||||||
|
/* duration: SHRINK_TIME */
|
||||||
|
-webkit-animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||||
|
animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes fade-out {
|
||||||
|
from { opacity: 1; }
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-out {
|
||||||
|
from { opacity: 1; }
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
34
app/static/external/materialize/sass/components/_pulse.scss
vendored
Normal file
34
app/static/external/materialize/sass/components/_pulse.scss
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.pulse {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: inherit;
|
||||||
|
border-radius: inherit;
|
||||||
|
transition: opacity .3s, transform .3s;
|
||||||
|
animation: pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-animation {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
}
|
216
app/static/external/materialize/sass/components/_sidenav.scss
vendored
Normal file
216
app/static/external/materialize/sass/components/_sidenav.scss
vendored
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
.sidenav {
|
||||||
|
position: fixed;
|
||||||
|
width: $sidenav-width;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
margin: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
height: 100%;
|
||||||
|
height: calc(100% + 60px);
|
||||||
|
height: -moz-calc(100%); //Temporary Firefox Fix
|
||||||
|
padding-bottom: 60px;
|
||||||
|
background-color: $sidenav-bg-color;
|
||||||
|
z-index: 999;
|
||||||
|
overflow-y: auto;
|
||||||
|
will-change: transform;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateX(-105%);
|
||||||
|
|
||||||
|
@extend .z-depth-1;
|
||||||
|
|
||||||
|
// Right Align
|
||||||
|
&.right-aligned {
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(105%);
|
||||||
|
left: auto;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
li {
|
||||||
|
float: none;
|
||||||
|
line-height: $sidenav-line-height;
|
||||||
|
|
||||||
|
&.active { background-color: rgba(0,0,0,.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
li > a {
|
||||||
|
color: $sidenav-font-color;
|
||||||
|
display: block;
|
||||||
|
font-size: $sidenav-font-size;
|
||||||
|
font-weight: 500;
|
||||||
|
height: $sidenav-item-height;
|
||||||
|
line-height: $sidenav-line-height;
|
||||||
|
padding: 0 ($sidenav-padding * 2);
|
||||||
|
|
||||||
|
&:hover { background-color: rgba(0,0,0,.05);}
|
||||||
|
|
||||||
|
&.btn, &.btn-large, &.btn-flat, &.btn-floating {
|
||||||
|
margin: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn,
|
||||||
|
&.btn-large,
|
||||||
|
&.btn-floating { color: $button-raised-color; }
|
||||||
|
&.btn-flat { color: $button-flat-color; }
|
||||||
|
|
||||||
|
&.btn:hover,
|
||||||
|
&.btn-large:hover { background-color: lighten($button-raised-background, 5%); }
|
||||||
|
&.btn-floating:hover { background-color: $button-raised-background; }
|
||||||
|
|
||||||
|
& > i,
|
||||||
|
& > [class^="mdi-"], li > a > [class*="mdi-"],
|
||||||
|
& > i.material-icons {
|
||||||
|
float: left;
|
||||||
|
height: $sidenav-item-height;
|
||||||
|
line-height: $sidenav-line-height;
|
||||||
|
margin: 0 ($sidenav-padding * 2) 0 0;
|
||||||
|
width: $sidenav-item-height / 2;
|
||||||
|
color: rgba(0,0,0,.54);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
margin: ($sidenav-padding / 2) 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheader {
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor: initial;
|
||||||
|
pointer-events: none;
|
||||||
|
color: rgba(0,0,0,.54);
|
||||||
|
font-size: $sidenav-font-size;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: $sidenav-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-view {
|
||||||
|
position: relative;
|
||||||
|
padding: ($sidenav-padding * 2) ($sidenav-padding * 2) 0;
|
||||||
|
margin-bottom: $sidenav-padding / 2;
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
&:hover { background-color: transparent; }
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle, .name, .email {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
height: 64px;
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name,
|
||||||
|
.email {
|
||||||
|
font-size: $sidenav-font-size;
|
||||||
|
line-height: $sidenav-line-height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Touch interaction
|
||||||
|
.drag-target {
|
||||||
|
// Right Align
|
||||||
|
&.right-aligned {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 10px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fixed Sidenav shown
|
||||||
|
.sidenav.sidenav-fixed {
|
||||||
|
// Right Align
|
||||||
|
&.right-aligned {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
left: 0;
|
||||||
|
transform: translateX(0);
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed Sidenav hide on smaller
|
||||||
|
@media #{$medium-and-down} {
|
||||||
|
.sidenav {
|
||||||
|
&.sidenav-fixed {
|
||||||
|
transform: translateX(-105%);
|
||||||
|
|
||||||
|
&.right-aligned {
|
||||||
|
transform: translateX(105%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
padding: 0 $sidenav-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-view {
|
||||||
|
padding: $sidenav-padding $sidenav-padding 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sidenav .collapsible-body > ul:not(.collapsible) > li.active,
|
||||||
|
.sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active {
|
||||||
|
background-color: $primary-color;
|
||||||
|
a {
|
||||||
|
color: $sidenav-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidenav .collapsible-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sidenav-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
opacity: 0;
|
||||||
|
height: 120vh;
|
||||||
|
background-color: rgba(0,0,0,.5);
|
||||||
|
z-index: 997;
|
||||||
|
display: none;
|
||||||
|
}
|
92
app/static/external/materialize/sass/components/_slider.scss
vendored
Normal file
92
app/static/external/materialize/sass/components/_slider.scss
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
.slider {
|
||||||
|
position: relative;
|
||||||
|
height: 400px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// Fullscreen slider
|
||||||
|
&.fullscreen {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
ul.slides {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.indicators {
|
||||||
|
z-index: 2;
|
||||||
|
bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slides {
|
||||||
|
background-color: $slider-bg-color;
|
||||||
|
margin: 0;
|
||||||
|
height: 400px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: inherit;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
left: 15%;
|
||||||
|
width: 70%;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
p { color: $slider-bg-color-light; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.indicators {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.indicator-item {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin: 0 12px;
|
||||||
|
background-color: $slider-bg-color-light;
|
||||||
|
|
||||||
|
transition: background-color .3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $slider-indicator-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
app/static/external/materialize/sass/components/_table_of_contents.scss
vendored
Normal file
33
app/static/external/materialize/sass/components/_table_of_contents.scss
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/***************
|
||||||
|
Nav List
|
||||||
|
***************/
|
||||||
|
.table-of-contents {
|
||||||
|
&.fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #757575;
|
||||||
|
padding-left: 16px;
|
||||||
|
height: 1.5rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
letter-spacing: .4;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: lighten(#757575, 20%);
|
||||||
|
padding-left: 15px;
|
||||||
|
border-left: 1px solid $primary-color;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
font-weight: 500;
|
||||||
|
padding-left: 14px;
|
||||||
|
border-left: 2px solid $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user