WIP: Use Flask-Assets to merge js, css and less files
Should reduce number of requests and hence load times needed for loading web interface. TODO: Handling of empty bundles needs to be fixed
This commit is contained in:
parent
db980689c8
commit
157b78a052
9 changed files with 246 additions and 84 deletions
1
setup.py
1
setup.py
|
|
@ -21,6 +21,7 @@ INSTALL_REQUIRES = [
|
|||
"Flask-Login==0.2.2",
|
||||
"Flask-Principal==0.3.5",
|
||||
"Flask-Babel==0.9",
|
||||
"Flask-Assets",
|
||||
"pyserial",
|
||||
"netaddr",
|
||||
"watchdog",
|
||||
|
|
|
|||
|
|
@ -648,12 +648,24 @@ class BlueprintPlugin(OctoPrintPlugin, RestartNeedingPlugin):
|
|||
``template_folder``, etc.
|
||||
|
||||
Defaults to the blueprint's ``static_folder`` and ``template_folder`` to be set to the plugin's basefolder
|
||||
plus ``/static`` or respectively ``/templates``.
|
||||
plus ``/static`` or respectively ``/templates``, or -- if the plugin also implements :class:`AssetPlugin` and/or
|
||||
:class:`TemplatePlugin` -- the paths provided by ``get_asset_folder`` and ``get_template_folder`` respectively.
|
||||
"""
|
||||
import os
|
||||
|
||||
if isinstance(self, AssetPlugin):
|
||||
static_folder = self.get_asset_folder()
|
||||
else:
|
||||
static_folder = os.path.join(self._basefolder, "static")
|
||||
|
||||
if isinstance(self, TemplatePlugin):
|
||||
template_folder = self.get_template_folder()
|
||||
else:
|
||||
template_folder = os.path.join(self._basefolder, "templates")
|
||||
|
||||
return dict(
|
||||
static_folder=os.path.join(self._basefolder, "static"),
|
||||
template_folder=os.path.join(self._basefolder, "templates")
|
||||
static_folder=static_folder,
|
||||
template_folder=template_folder
|
||||
)
|
||||
|
||||
def is_blueprint_protected(self):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
|||
|
||||
import uuid
|
||||
from sockjs.tornado import SockJSRouter
|
||||
from flask import Flask, g, request, session
|
||||
from flask import Flask, g, request, session, Blueprint
|
||||
from flask.ext.login import LoginManager, current_user
|
||||
from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded, UserNeed
|
||||
from flask.ext.babel import Babel, gettext, ngettext
|
||||
from flask.ext.assets import Environment, Bundle
|
||||
from babel import Locale
|
||||
from watchdog.observers import Observer
|
||||
from collections import defaultdict
|
||||
|
|
@ -24,6 +25,7 @@ SUCCESS = {}
|
|||
NO_CONTENT = ("", 204)
|
||||
|
||||
app = Flask("octoprint")
|
||||
assets = None
|
||||
babel = None
|
||||
debug = False
|
||||
|
||||
|
|
@ -232,6 +234,9 @@ class Server():
|
|||
pluginLifecycleManager.add_callback("enabled", template_enabled)
|
||||
pluginLifecycleManager.add_callback("disabled", template_disabled)
|
||||
|
||||
# setup assets
|
||||
self._setup_assets()
|
||||
|
||||
# configure timelapse
|
||||
octoprint.timelapse.configureTimelapse()
|
||||
|
||||
|
|
@ -240,6 +245,7 @@ class Server():
|
|||
if self._debug:
|
||||
events.DebugEventListener()
|
||||
|
||||
# setup access control
|
||||
if s.getBoolean(["accessControl", "enabled"]):
|
||||
userManagerName = s.get(["accessControl", "userManager"])
|
||||
try:
|
||||
|
|
@ -300,10 +306,14 @@ class Server():
|
|||
upload_suffixes = dict(name=s.get(["server", "uploads", "nameSuffix"]), path=s.get(["server", "uploads", "pathSuffix"]))
|
||||
|
||||
server_routes = self._router.urls + [
|
||||
# various downloads
|
||||
(r"/downloads/timelapse/([^/]*\.mpg)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("timelapse"), as_attachment=True)),
|
||||
(r"/downloads/files/local/(.*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("uploads"), as_attachment=True, path_validation=util.tornado.path_validation_factory(lambda path: not os.path.basename(path).startswith("."), status_code=404))),
|
||||
(r"/downloads/logs/([^/]*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("logs"), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.admin_validator))),
|
||||
# camera snapshot
|
||||
(r"/downloads/camera/current", util.tornado.UrlForwardHandler, dict(url=s.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))),
|
||||
# generated webassets
|
||||
(r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=s.getBaseFolder("webassets")))
|
||||
]
|
||||
for name, hook in pluginManager.get_hooks("octoprint.server.http.routes").items():
|
||||
try:
|
||||
|
|
@ -610,11 +620,21 @@ class Server():
|
|||
# also register any blueprints defined in BlueprintPlugins
|
||||
self._register_blueprint_plugins()
|
||||
|
||||
# and register a blueprint for serving the static files of asset plugins which are not blueprint plugins themselves
|
||||
self._register_asset_plugins()
|
||||
|
||||
def _register_blueprint_plugins(self):
|
||||
blueprint_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.BlueprintPlugin)
|
||||
for plugin in blueprint_plugins:
|
||||
self._register_blueprint_plugin(plugin)
|
||||
|
||||
def _register_asset_plugins(self):
|
||||
asset_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.AssetPlugin)
|
||||
for plugin in asset_plugins:
|
||||
if isinstance(plugin, octoprint.plugin.BlueprintPlugin):
|
||||
continue
|
||||
self._register_asset_plugin(plugin)
|
||||
|
||||
def _register_blueprint_plugin(self, plugin):
|
||||
name = plugin._identifier
|
||||
blueprint = plugin.get_blueprint()
|
||||
|
|
@ -632,6 +652,100 @@ class Server():
|
|||
if self._logger:
|
||||
self._logger.debug("Registered API of plugin {name} under URL prefix {url_prefix}".format(name=name, url_prefix=url_prefix))
|
||||
|
||||
def _register_asset_plugin(self, plugin):
|
||||
name = plugin._identifier
|
||||
|
||||
url_prefix = "/plugin/{name}".format(name=name)
|
||||
blueprint = Blueprint("plugin." + name, name, static_folder=plugin.get_asset_folder())
|
||||
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
||||
|
||||
if self._logger:
|
||||
self._logger.debug("Registered assets of plugin {name} under URL prefix {url_prefix}".format(name=name, url_prefix=url_prefix))
|
||||
|
||||
def _setup_assets(self):
|
||||
global app
|
||||
global assets
|
||||
global pluginManager
|
||||
|
||||
AdjustedEnvironment = type(Environment)(Environment.__name__, (Environment,), dict(
|
||||
resolver_class=util.flask.PluginAssetResolver
|
||||
))
|
||||
assets = AdjustedEnvironment(app)
|
||||
|
||||
dynamic_assets = util.flask.collect_plugin_assets()
|
||||
|
||||
js_libs = [
|
||||
"js/lib/jquery/jquery.min.js",
|
||||
"js/lib/modernizr.custom.js",
|
||||
"js/lib/lodash.min.js",
|
||||
"js/lib/sprintf.min.js",
|
||||
"js/lib/knockout.js",
|
||||
"js/lib/knockout.mapping-latest.js",
|
||||
"js/lib/babel.js",
|
||||
"js/lib/avltree.js",
|
||||
"js/lib/bootstrap/bootstrap.js",
|
||||
"js/lib/bootstrap/bootstrap-modalmanager.js",
|
||||
"js/lib/bootstrap/bootstrap-modal.js",
|
||||
"js/lib/bootstrap/bootstrap-slider.js",
|
||||
"js/lib/bootstrap/bootstrap-tabdrop.js",
|
||||
"js/lib/jquery/jquery.ui.core.js",
|
||||
"js/lib/jquery/jquery.ui.widget.js",
|
||||
"js/lib/jquery/jquery.ui.mouse.js",
|
||||
"js/lib/jquery/jquery.flot.js",
|
||||
"js/lib/jquery/jquery.iframe-transport.js",
|
||||
"js/lib/jquery/jquery.fileupload.js",
|
||||
"js/lib/jquery/jquery.slimscroll.min.js",
|
||||
"js/lib/jquery/jquery.qrcode.min.js",
|
||||
"js/lib/sockjs-0.3.4.min.js",
|
||||
"js/lib/moment-with-locales.min.js",
|
||||
"js/lib/pusher.color.min.js",
|
||||
"js/lib/detectmobilebrowser.js",
|
||||
"js/lib/md5.min.js",
|
||||
"js/lib/pnotify.min.js",
|
||||
"js/lib/bootstrap-slider-knockout-binding.js",
|
||||
"js/lib/loglevel.min.js"
|
||||
]
|
||||
|
||||
js_app = dynamic_assets["js"] + [
|
||||
"js/app/dataupdater.js",
|
||||
"js/app/helpers.js",
|
||||
"js/app/main.js",
|
||||
]
|
||||
|
||||
css_libs = [
|
||||
"css/bootstrap.min.css",
|
||||
"css/bootstrap-modal.css",
|
||||
"css/bootstrap-slider.css",
|
||||
"css/bootstrap-tabdrop.css",
|
||||
"css/font-awesome.min.css",
|
||||
"css/jquery.fileupload-ui.css",
|
||||
"css/pnotify.min.css"
|
||||
]
|
||||
|
||||
css_app = []
|
||||
less_app = []
|
||||
for sheet, path in dynamic_assets["stylesheets"]:
|
||||
if sheet == "css":
|
||||
css_app.append(path)
|
||||
elif sheet == "less":
|
||||
less_app.append(path)
|
||||
|
||||
js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js")
|
||||
js_app_bundle = Bundle(*js_app, output="webassets/package_app.js")
|
||||
css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css")
|
||||
|
||||
assets.register("js_libs", js_libs_bundle)
|
||||
assets.register("js_app", js_app_bundle)
|
||||
assets.register("css_libs", css_libs_bundle)
|
||||
|
||||
if len(css_app):
|
||||
css_app_bundle = Bundle(*css_app, output="webassets/packed_app.css")
|
||||
assets.register("css_app", css_app_bundle)
|
||||
if len(less_app):
|
||||
less_app_bundle = Bundle(*less_app, output="webassets/packed_app.less")
|
||||
assets.register("less_app", less_app_bundle)
|
||||
|
||||
|
||||
class LifecycleManager(object):
|
||||
def __init__(self, plugin_manager):
|
||||
self._plugin_manager = plugin_manager
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ def apiKeyRequestHandler():
|
|||
if _flask.request.method == 'OPTIONS' and settings().getBoolean(["api", "allowCrossOrigin"]):
|
||||
return optionsAllowOrigin(_flask.request)
|
||||
|
||||
if _flask.request.endpoint == "static" or _flask.request.endpoint.endswith(".static"):
|
||||
return
|
||||
|
||||
apikey = get_api_key(_flask.request)
|
||||
if apikey is None:
|
||||
# no api key => 401
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import tornado.web
|
|||
import flask
|
||||
import flask.ext.login
|
||||
import flask.ext.principal
|
||||
import flask.ext.assets
|
||||
import functools
|
||||
import time
|
||||
import uuid
|
||||
|
|
@ -20,6 +21,7 @@ import netaddr
|
|||
from octoprint.settings import settings
|
||||
import octoprint.server
|
||||
import octoprint.users
|
||||
import octoprint.plugin
|
||||
|
||||
from werkzeug.contrib.cache import SimpleCache
|
||||
|
||||
|
|
@ -32,8 +34,6 @@ def enable_additional_translations(default_locale="en", additional_folders=None)
|
|||
from babel import support, Locale
|
||||
import flask.ext.babel
|
||||
|
||||
import octoprint.plugin
|
||||
|
||||
if additional_folders is None:
|
||||
additional_folders = []
|
||||
|
||||
|
|
@ -411,3 +411,91 @@ def get_json_command_from_request(request, valid_commands):
|
|||
return None, None, make_response("Mandatory parameter %s missing for command %s" % (parameter, command), 400)
|
||||
|
||||
return command, data, None
|
||||
|
||||
##~~ Flask-Assets resolver with plugin asset support
|
||||
|
||||
class PluginAssetResolver(flask.ext.assets.FlaskResolver):
|
||||
|
||||
def split_prefix(self, item):
|
||||
if item.startswith("plugin/"):
|
||||
try:
|
||||
prefix, plugin, name = item.split("/", 2)
|
||||
blueprint = prefix + "." + plugin
|
||||
|
||||
directory = flask.ext.assets.get_static_folder(self.env._app.blueprints[blueprint])
|
||||
item = name
|
||||
return directory, item
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
return flask.ext.assets.FlaskResolver.split_prefix(self, item)
|
||||
|
||||
def resolve_output_to_path(self, target, bundle):
|
||||
if target.startswith("webassets/"):
|
||||
import os
|
||||
return os.path.normpath(os.path.join(settings().getBaseFolder("webassets"), target[len("webassets/"):]))
|
||||
return flask.ext.assets.FlaskResolver.resolve_output_to_path(self, target, bundle)
|
||||
|
||||
##~~ plugin assets collector
|
||||
|
||||
def collect_plugin_assets(enable_gcodeviewer=True, enable_timelapse=True, preferred_stylesheet="css"):
|
||||
supported_stylesheets = ("css", "less")
|
||||
assets = dict(
|
||||
js=[],
|
||||
stylesheets=[]
|
||||
)
|
||||
assets["js"] = [
|
||||
'js/app/viewmodels/appearance.js',
|
||||
'js/app/viewmodels/connection.js',
|
||||
'js/app/viewmodels/control.js',
|
||||
'js/app/viewmodels/firstrun.js',
|
||||
'js/app/viewmodels/files.js',
|
||||
'js/app/viewmodels/loginstate.js',
|
||||
'js/app/viewmodels/navigation.js',
|
||||
'js/app/viewmodels/printerstate.js',
|
||||
'js/app/viewmodels/printerprofiles.js',
|
||||
'js/app/viewmodels/settings.js',
|
||||
'js/app/viewmodels/slicing.js',
|
||||
'js/app/viewmodels/temperature.js',
|
||||
'js/app/viewmodels/terminal.js',
|
||||
'js/app/viewmodels/users.js',
|
||||
'js/app/viewmodels/log.js',
|
||||
'js/app/viewmodels/usersettings.js'
|
||||
]
|
||||
if enable_gcodeviewer:
|
||||
assets["js"] += [
|
||||
'js/app/viewmodels/gcode.js',
|
||||
'gcodeviewer/js/ui.js',
|
||||
'gcodeviewer/js/gCodeReader.js',
|
||||
'gcodeviewer/js/renderer.js'
|
||||
]
|
||||
if enable_timelapse:
|
||||
assets["js"].append('js/app/viewmodels/timelapse.js')
|
||||
|
||||
if preferred_stylesheet == "less":
|
||||
assets["stylesheets"].append(("less", 'less/octoprint.less'))
|
||||
elif preferred_stylesheet == "css":
|
||||
assets["stylesheets"].append(("css", 'css/octoprint.css'))
|
||||
|
||||
asset_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.AssetPlugin)
|
||||
for implementation in asset_plugins:
|
||||
name = implementation._identifier
|
||||
all_assets = implementation.get_assets()
|
||||
|
||||
if "js" in all_assets:
|
||||
for asset in all_assets["js"]:
|
||||
assets["js"].append('plugin/{name}/{asset}'.format(**locals()))
|
||||
|
||||
if preferred_stylesheet in all_assets:
|
||||
for asset in all_assets[preferred_stylesheet]:
|
||||
assets["stylesheets"].append((preferred_stylesheet, 'plugin/{name}/{asset}'.format(**locals())))
|
||||
else:
|
||||
for stylesheet in supported_stylesheets:
|
||||
if not stylesheet in all_assets:
|
||||
continue
|
||||
|
||||
for asset in all_assets[stylesheet]:
|
||||
assets["stylesheets"].append((stylesheet, 'plugin/{name}/{asset}'.format(**locals())))
|
||||
break
|
||||
|
||||
return assets
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms
|
|||
import os
|
||||
|
||||
from collections import defaultdict
|
||||
from flask import request, g, url_for, make_response, render_template, send_from_directory
|
||||
from flask import request, g, url_for, make_response, render_template, send_from_directory, redirect
|
||||
|
||||
import octoprint.plugin
|
||||
|
||||
|
|
@ -491,19 +491,6 @@ def localeJs(locale, domain):
|
|||
|
||||
@app.route("/plugin_assets/<string:name>/<path:filename>")
|
||||
def plugin_assets(name, filename):
|
||||
asset_plugins = pluginManager.get_filtered_implementations(lambda p: p._identifier == name, octoprint.plugin.AssetPlugin)
|
||||
|
||||
if not asset_plugins:
|
||||
return make_response("Asset not found", 404)
|
||||
|
||||
if len(asset_plugins) > 1:
|
||||
return make_response("More than one asset provider for {name}, can't proceed".format(name=name), 500)
|
||||
|
||||
asset_plugin = asset_plugins[0]
|
||||
asset_folder = asset_plugin.get_asset_folder()
|
||||
if asset_folder is None:
|
||||
return make_response("Asset not found", 404)
|
||||
|
||||
return send_from_directory(asset_folder, filename)
|
||||
return redirect(url_for("plugin." + name + ".static", filename=filename))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,8 @@ default_settings = {
|
|||
"slicingProfiles": None,
|
||||
"printerProfiles": None,
|
||||
"scripts": None,
|
||||
"translations": None
|
||||
"translations": None,
|
||||
"webassets": None
|
||||
},
|
||||
"temperature": {
|
||||
"profiles": [
|
||||
|
|
|
|||
|
|
@ -1,48 +1,11 @@
|
|||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/modernizr.custom.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/lodash.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sprintf.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.mapping-latest.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/babel.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/avltree.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modalmanager.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modal.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-slider.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-tabdrop.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.core.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.widget.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.ui.mouse.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.flot.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.iframe-transport.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.fileupload.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.slimscroll.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.qrcode.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sockjs-0.3.4.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment-with-locales.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pusher.color.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/detectmobilebrowser.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/md5.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pnotify.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap-slider-knockout-binding.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/loglevel.min.js') }}"></script>
|
||||
{% assets "js_libs" %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
|
||||
<!-- Include OctoPrint files -->
|
||||
<!-- TODO: merge/minimize in the future -->
|
||||
{% for url in assets["js"] %}
|
||||
<script type="text/javascript" src="{{ url }}"></script>
|
||||
{% endfor %}
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/app/dataupdater.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/app/helpers.js') }}"></script>
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/app/main.js') }}"></script>
|
||||
<!-- /Include OctoPrint files -->
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/ui.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/gCodeReader.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/renderer.js') }}"></script>
|
||||
{% assets "js_app" %}
|
||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
||||
{% endassets %}
|
||||
|
||||
{% if g.locale %}
|
||||
<script type="text/javascript" src="{{ url_for('localeJs', locale=g.locale, domain='messages') }}"></script>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/bootstrap-modal.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/bootstrap-slider.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/bootstrap-tabdrop.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/jquery.fileupload-ui.css') }}" rel="stylesheet" media="screen">
|
||||
<link href="{{ url_for('static', filename='css/pnotify.min.css') }}" rel="stylesheet" media="screen">
|
||||
{% assets "css_libs" %}
|
||||
<link href="{{ ASSET_URL }}" rel="stylesheet" media="screen">
|
||||
{% endassets %}
|
||||
|
||||
{% set lessneeded=[] %}
|
||||
{% for type, url in assets["stylesheets"] %}
|
||||
{% if type == "css" %}
|
||||
<link href="{{ url }}" rel="stylesheet" type="text/css" media="screen">
|
||||
{% elif type == "less" %}
|
||||
{% do lessneeded.append(1) %}
|
||||
<link href="{{ url }}" rel="stylesheet/less" type="text/css" media="screen">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if lessneeded %}
|
||||
<script src="{{ url_for('static', filename='js/lib/less.min.js') }}" type="text/javascript"></script>
|
||||
{% endif %}
|
||||
{% assets "css_app" %}
|
||||
<link href="{{ ASSET_URL }}" rel="stylesheet" type="text/css" media="screen">
|
||||
{% endassets %}
|
||||
|
||||
{% assets "less_app" %}
|
||||
<link href="{{ url }}" rel="stylesheet/less" type="text/css" media="screen">
|
||||
{% endassets %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/lib/less.min.js') }}" type="text/javascript"></script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue