diff --git a/babel.cfg b/babel.cfg
index dcdd8e34..014eb25d 100644
--- a/babel.cfg
+++ b/babel.cfg
@@ -1,7 +1,7 @@
[python: src/octoprint/**.py]
[jinja2: src/octoprint/templates/**.jinja2]
[jinja2: src/octoprint/plugins/**.jinja2]
-extensions=jinja2.ext.autoescape, jinja2.ext.with_
+extensions=jinja2.ext.autoescape, jinja2.ext.with_, webassets.ext.jinja2.AssetsExtension
[javascript: src/octoprint/static/js/app/**.js]
[javascript: src/octoprint/plugins/**.js]
diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py
index 5701f6b4..56b2ddd7 100644
--- a/src/octoprint/server/__init__.py
+++ b/src/octoprint/server/__init__.py
@@ -290,11 +290,6 @@ class Server():
# register API blueprint
self._setup_blueprints()
- def blueprint_enabled(name, plugin):
- if plugin.implementation is None or not isinstance(plugin.implementation, octoprint.plugin.BlueprintPlugin):
- return
- self._register_blueprint_plugin(plugin.implementation)
- pluginLifecycleManager.add_callback(["enabled"], blueprint_enabled)
## Tornado initialization starts here
@@ -313,7 +308,7 @@ class Server():
# 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")))
+ (r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(s.getBaseFolder("generated"), "webassets")))
]
for name, hook in pluginManager.get_hooks("octoprint.server.http.routes").items():
try:
@@ -667,12 +662,28 @@ class Server():
global assets
global pluginManager
+ base_folder = settings().getBaseFolder("generated")
+
AdjustedEnvironment = type(Environment)(Environment.__name__, (Environment,), dict(
resolver_class=util.flask.PluginAssetResolver
))
- assets = AdjustedEnvironment(app)
+ class CustomDirectoryEnvironment(AdjustedEnvironment):
+ @property
+ def directory(self):
+ return base_folder
- dynamic_assets = util.flask.collect_plugin_assets()
+ assets = CustomDirectoryEnvironment(app)
+ assets.debug = not settings().getBoolean(["devel", "webassets", "bundle"])
+
+ enable_gcodeviewer = settings().getBoolean(["gcodeViewer", "enabled"])
+ enable_timelapse = (settings().get(["webcam", "snapshot"]) and settings().get(["webcam", "ffmpeg"]))
+ preferred_stylesheet = settings().get(["devel", "stylesheet"])
+
+ dynamic_assets = util.flask.collect_plugin_assets(
+ enable_gcodeviewer=enable_gcodeviewer,
+ enable_timelapse=enable_timelapse,
+ preferred_stylesheet=preferred_stylesheet
+ )
js_libs = [
"js/lib/jquery/jquery.min.js",
@@ -696,16 +707,15 @@ class Server():
"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/lib/loglevel.min.js",
+ "js/lib/sockjs-0.3.4.min.js"
]
-
js_app = dynamic_assets["js"] + [
"js/app/dataupdater.js",
"js/app/helpers.js",
@@ -721,29 +731,24 @@ class Server():
"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)
+ css_app = ["empty"] + dynamic_assets["css"]
+ less_app = ["empty"] + dynamic_assets["less"]
js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js")
- js_app_bundle = Bundle(*js_app, output="webassets/package_app.js")
+ if settings().getBoolean(["devel", "webassets", "minify"]):
+ js_app_bundle = Bundle(*js_app, output="webassets/package_app.js", filters="rjsmin")
+ else:
+ js_app_bundle = Bundle(*js_app, output="webassets/package_app.js")
+ all_js_bundle = Bundle(js_libs_bundle, js_app_bundle, output="webassets/packed.js")
+
css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css")
+ css_app_bundle = Bundle(*css_app, output="webassets/packed_app.css")
+ all_css_bundle = Bundle(css_libs_bundle, css_app_bundle, output="webassets/packed.css")
+ all_less_bundle = Bundle(*less_app, output="webassets/packed_app.less")
- 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)
+ assets.register("all_js", all_js_bundle)
+ assets.register("all_css", all_css_bundle)
+ assets.register("less_app", all_less_bundle)
class LifecycleManager(object):
diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py
index 6ff85474..85c86089 100644
--- a/src/octoprint/server/util/flask.py
+++ b/src/octoprint/server/util/flask.py
@@ -431,10 +431,34 @@ class PluginAssetResolver(flask.ext.assets.FlaskResolver):
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)
+ import os
+ return os.path.normpath(os.path.join(self.env.directory, target))
+
+ def resolve_source_to_url(self, filepath, item):
+ if item.startswith("plugin/"):
+ try:
+ prefix, plugin, name = item.split('/', 2)
+ blueprint = prefix + "." + plugin
+
+ self.env._app.blueprints[blueprint] # keyerror if no module
+ endpoint = '%s.static' % blueprint
+ filename = name
+ except (ValueError, KeyError):
+ endpoint = 'static'
+ filename = item
+
+ ctx = None
+ if not flask._request_ctx_stack.top:
+ ctx = self.env._app.test_request_context()
+ ctx.push()
+ try:
+ return flask.url_for(endpoint, filename=filename)
+ finally:
+ if ctx:
+ ctx.pop()
+
+ return flask.ext.assets.FlaskResolver.resolve_source_to_url(self, filepath, item)
+
##~~ plugin assets collector
@@ -442,7 +466,8 @@ def collect_plugin_assets(enable_gcodeviewer=True, enable_timelapse=True, prefer
supported_stylesheets = ("css", "less")
assets = dict(
js=[],
- stylesheets=[]
+ css=[],
+ less=[]
)
assets["js"] = [
'js/app/viewmodels/appearance.js',
@@ -473,9 +498,9 @@ def collect_plugin_assets(enable_gcodeviewer=True, enable_timelapse=True, prefer
assets["js"].append('js/app/viewmodels/timelapse.js')
if preferred_stylesheet == "less":
- assets["stylesheets"].append(("less", 'less/octoprint.less'))
+ assets["less"].append('less/octoprint.less')
elif preferred_stylesheet == "css":
- assets["stylesheets"].append(("css", 'css/octoprint.css'))
+ assets["css"].append('css/octoprint.css')
asset_plugins = octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.AssetPlugin)
for implementation in asset_plugins:
@@ -488,14 +513,14 @@ def collect_plugin_assets(enable_gcodeviewer=True, enable_timelapse=True, prefer
if preferred_stylesheet in all_assets:
for asset in all_assets[preferred_stylesheet]:
- assets["stylesheets"].append((preferred_stylesheet, 'plugin/{name}/{asset}'.format(**locals())))
+ assets[preferred_stylesheet].append('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())))
+ assets[stylesheet].append('plugin/{name}/{asset}'.format(**locals()))
break
return assets
diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py
index e65e0037..80a78cad 100644
--- a/src/octoprint/settings.py
+++ b/src/octoprint/settings.py
@@ -156,7 +156,7 @@ default_settings = {
"printerProfiles": None,
"scripts": None,
"translations": None,
- "webassets": None
+ "generated": None
},
"temperature": {
"profiles": [
@@ -249,6 +249,10 @@ default_settings = {
"cache": {
"enabled": True
},
+ "webassets": {
+ "minify": True,
+ "bundle": True
+ },
"virtualPrinter": {
"enabled": False,
"okAfterResend": False,
diff --git a/src/octoprint/static/empty b/src/octoprint/static/empty
new file mode 100644
index 00000000..e69de29b
diff --git a/src/octoprint/templates/javascripts.jinja2 b/src/octoprint/templates/javascripts.jinja2
index ccfa4ae7..f285bb11 100644
--- a/src/octoprint/templates/javascripts.jinja2
+++ b/src/octoprint/templates/javascripts.jinja2
@@ -1,9 +1,4 @@
-
-{% assets "js_libs" %}
-
-{% endassets %}
-
-{% assets "js_app" %}
+{% assets "all_js" %}
{% endassets %}
diff --git a/src/octoprint/templates/stylesheets.jinja2 b/src/octoprint/templates/stylesheets.jinja2
index 8516f823..75337db4 100644
--- a/src/octoprint/templates/stylesheets.jinja2
+++ b/src/octoprint/templates/stylesheets.jinja2
@@ -1,13 +1,9 @@
-{% assets "css_libs" %}
+{% assets "all_css" %}
{% endassets %}
-{% assets "css_app" %}
-
-{% endassets %}
-
{% assets "less_app" %}
-
+
{% endassets %}