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 %}