diff --git a/CHANGES.rst b/CHANGES.rst index 816536a1ff..a02b05bb2a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,7 @@ Changelog **Changed** +- #1513 Better Ajax Loader for Sample Add Form - #1508 Do not try to render InstrumentQCFailuresViewlet to non-lab personnel - #1495 Better Remarks handling and display - #1502 Improved DateTime Widget diff --git a/bika/lims/browser/bootstrap.py b/bika/lims/browser/bootstrap.py new file mode 100644 index 0000000000..1380cef9d4 --- /dev/null +++ b/bika/lims/browser/bootstrap.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SENAITE.CORE. +# +# SENAITE.LIMS is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright 2018-2020 by it's authors. +# Some rights reserved, see README and LICENSE. + +from bika.lims import api +from plone.memoize.ram import cache +from Products.Five import BrowserView +from senaite.lims import logger +from zope.component import getMultiAdapter +from zope.interface import Interface +from zope.interface import implements + + +class IBootstrapView(Interface): + """Twitter Bootstrap View + """ + + def getColumnsClasses(view=None): + """A helper method to return the clases for the columns of the site + it should return a dict with three elements:'one', 'two', 'content' + Each of them should contain the classnames for the first (leftmost) + second (rightmost) and middle column + """ + + def getViewportValues(view=None): + """Determine the value of the viewport meta-tag + """ + + +def icon_cache_key(method, self, brain_or_object): + """Generates a cache key for the icon lookup + + Includes the virtual URL to handle multiple HTTP/HTTPS domains + Example: http://senaite.local/clients?modified=1512033263370 + """ + url = api.get_url(brain_or_object) + modified = api.get_modification_date(brain_or_object).millis() + key = "{}?modified={}".format(url, modified) + logger.debug("Generated Cache Key: {}".format(key)) + return key + + +class BootstrapView(BrowserView): + """Twitter Bootstrap helper view for SENAITE LIMS + """ + implements(IBootstrapView) + + def __init__(self, context, request): + super(BrowserView, self).__init__(context, request) + + @cache(icon_cache_key) + def get_icon_for(self, brain_or_object): + """Get the navigation portlet icon for the brain or object + + The cache key ensures that the lookup is done only once per domain name + """ + portal_types = api.get_tool("portal_types") + fti = portal_types.getTypeInfo(api.get_portal_type(brain_or_object)) + icon = fti.getIcon() + if not icon: + return "" + # Always try to get the big icon for high-res displays + icon_big = icon.replace(".png", "_big.png") + # fall back to a default icon if the looked up icon does not exist + if self.context.restrictedTraverse(icon_big, None) is None: + icon_big = None + portal_url = api.get_url(api.get_portal()) + title = api.get_title(brain_or_object) + html_tag = "".format( + title, portal_url, icon_big or icon) + logger.debug("Generated Icon Tag for {}: {}".format( + api.get_path(brain_or_object), html_tag)) + return html_tag + + def getViewportValues(self, view=None): + """Determine the value of the viewport meta-tag + """ + values = { + 'width': 'device-width', + 'initial-scale': '1.0', + } + + return ','.join('%s=%s' % (k, v) for k, v in values.items()) + + def getColumnsClasses(self, view=None): + """Determine whether a column should be shown. The left column is + called plone.leftcolumn; the right column is called + plone.rightcolumn. + """ + + plone_view = getMultiAdapter( + (self.context, self.request), name=u'plone') + portal_state = getMultiAdapter( + (self.context, self.request), name=u'plone_portal_state') + + sl = plone_view.have_portlets('plone.leftcolumn', view=view) + sr = plone_view.have_portlets('plone.rightcolumn', view=view) + + isRTL = portal_state.is_rtl() + + # pre-fill dictionary + columns = dict(one="", content="", two="") + + if not sl and not sr: + # we don't have columns, thus conten takes the whole width + columns['content'] = "col-md-12" + + elif sl and sr: + # In case we have both columns, content takes 50% of the whole + # width and the rest 50% is spread between the columns + columns['one'] = "col-xs-12 col-md-2" + columns['content'] = "col-xs-12 col-md-8" + columns['two'] = "col-xs-12 col-md-2" + + elif (sr and not sl) and not isRTL: + # We have right column and we are NOT in RTL language + columns['content'] = "col-xs-12 col-md-10" + columns['two'] = "col-xs-12 col-md-2" + + elif (sl and not sr) and isRTL: + # We have left column and we are in RTL language + columns['one'] = "col-xs-12 col-md-2" + columns['content'] = "col-xs-12 col-md-10" + + elif (sl and not sr) and not isRTL: + # We have left column and we are in NOT RTL language + columns['one'] = "col-xs-12 col-md-2" + columns['content'] = "col-xs-12 col-md-10" + + # # append cell to each css-string + # for key, value in columns.items(): + # columns[key] = "cell " + value + + return columns diff --git a/bika/lims/browser/bootstrap.zcml b/bika/lims/browser/bootstrap.zcml new file mode 100644 index 0000000000..bcd044a41d --- /dev/null +++ b/bika/lims/browser/bootstrap.zcml @@ -0,0 +1,17 @@ + + + + + + diff --git a/bika/lims/browser/configure.zcml b/bika/lims/browser/configure.zcml index a7eb065517..49ffcdd5d6 100644 --- a/bika/lims/browser/configure.zcml +++ b/bika/lims/browser/configure.zcml @@ -16,9 +16,11 @@ + + diff --git a/bika/lims/browser/controlpanel.py b/bika/lims/browser/controlpanel.py new file mode 100644 index 0000000000..418e44bf60 --- /dev/null +++ b/bika/lims/browser/controlpanel.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SENAITE.CORE. +# +# SENAITE.LIMS is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright 2018-2020 by it's authors. +# Some rights reserved, see README and LICENSE. + +from plone.app.controlpanel.overview import OverviewControlPanel +from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile + + +class SenaiteOverviewControlPanel(OverviewControlPanel): + template = ViewPageTemplateFile( + "templates/plone.app.controlpanel.overview.pt") diff --git a/bika/lims/browser/controlpanel.zcml b/bika/lims/browser/controlpanel.zcml new file mode 100644 index 0000000000..13814ac408 --- /dev/null +++ b/bika/lims/browser/controlpanel.zcml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index f7881ff8f7..fb4cead80a 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -1250,9 +1250,10 @@ var button; console.debug("°°° on_ajax_start °°°"); button = $("input[name=save_button]"); - return button.prop({ + button.prop({ "disabled": true }); + return button[0].value = _("Loading ..."); }; AnalysisRequestAdd.prototype.on_ajax_end = function() { @@ -1263,9 +1264,10 @@ var button; console.debug("°°° on_ajax_end °°°"); button = $("input[name=save_button]"); - return button.prop({ + button.prop({ "disabled": false }); + return button[0].value = _("Save"); }; AnalysisRequestAdd.prototype.on_form_submit = function(event, callback) { diff --git a/bika/lims/browser/js/bika.lims.site.js b/bika/lims/browser/js/bika.lims.site.js index de056db9d3..2e7e28f9d1 100644 --- a/bika/lims/browser/js/bika.lims.site.js +++ b/bika/lims/browser/js/bika.lims.site.js @@ -8,9 +8,6 @@ window.SiteView = (function() { function SiteView() { - this.on_ajax_error = bind(this.on_ajax_error, this); - this.on_ajax_end = bind(this.on_ajax_end, this); - this.on_ajax_start = bind(this.on_ajax_start, this); this.on_service_info_click = bind(this.on_service_info_click, this); this.on_reference_definition_list_change = bind(this.on_reference_definition_list_change, this); this.on_numeric_field_keypress = bind(this.on_numeric_field_keypress, this); @@ -20,8 +17,6 @@ this.on_autocomplete_keydown = bind(this.on_autocomplete_keydown, this); this.on_date_range_end_change = bind(this.on_date_range_end_change, this); this.on_date_range_start_change = bind(this.on_date_range_start_change, this); - this.stop_spinner = bind(this.stop_spinner, this); - this.start_spinner = bind(this.start_spinner, this); this.notify_in_panel = bind(this.notify_in_panel, this); this.notificationPanel = bind(this.notificationPanel, this); this.set_cookie = bind(this.set_cookie, this); @@ -35,7 +30,6 @@ this.get_portal_url = bind(this.get_portal_url, this); this.init_referencedefinition = bind(this.init_referencedefinition, this); this.init_datepickers = bind(this.init_datepickers, this); - this.init_spinner = bind(this.init_spinner, this); this.init_client_add_overlay = bind(this.init_client_add_overlay, this); this.bind_eventhandler = bind(this.bind_eventhandler, this); this.load = bind(this.load, this); @@ -45,7 +39,6 @@ console.debug("SiteView::load"); jarn.i18n.loadCatalog('senaite.core'); this._ = window.jarn.i18n.MessageFactory("senaite.core"); - this.init_spinner(); this.init_client_add_overlay(); this.init_datepickers(); this.init_referencedefinition(); @@ -74,9 +67,17 @@ $("body").on("change", ".date_range_start", this.on_date_range_start_change); $("body").on("change", ".date_range_end", this.on_date_range_end_change); $("body").on("click", "a.service_info", this.on_service_info_click); - $(document).on("ajaxStart", this.on_ajax_start); - $(document).on("ajaxStop", this.on_ajax_end); - return $(document).on("ajaxError", this.on_ajax_error); + return $(document).on({ + ajaxStart: function() { + $("body").addClass("loading"); + }, + ajaxStop: function() { + $("body").removeClass("loading"); + }, + ajaxError: function() { + $("body").removeClass("loading"); + } + }); }; SiteView.prototype.init_client_add_overlay = function() { @@ -102,20 +103,6 @@ }); }; - SiteView.prototype.init_spinner = function() { - - /* - * Initialize Spinner Overlay - */ - console.debug("SiteView::init_spinner"); - $(document).unbind('ajaxStart'); - $(document).unbind('ajaxStop'); - $('#ajax-spinner').remove(); - this.counter = 0; - this.spinner = $("
"); - return this.spinner.appendTo('body').hide(); - }; - SiteView.prototype.init_datepickers = function() { /* @@ -356,39 +343,6 @@ }); }; - SiteView.prototype.start_spinner = function() { - - /* - * Start Spinner Overlay - */ - console.debug("SiteView::start_spinner"); - this.counter++; - this.timer = setTimeout(((function(_this) { - return function() { - if (_this.counter > 0) { - _this.spinner.show('fast'); - } - }; - })(this)), 500); - }; - - SiteView.prototype.stop_spinner = function() { - - /* - * Stop Spinner Overlay - */ - console.debug("SiteView::stop_spinner"); - this.counter--; - if (this.counter < 0) { - this.counter = 0; - } - if (this.counter === 0) { - clearTimeout(this.timer); - this.spinner.stop(); - this.spinner.hide(); - } - }; - /* EVENT HANDLER */ @@ -603,34 +557,6 @@ return $(el).click(); }; - SiteView.prototype.on_ajax_start = function(event) { - - /* - * Eventhandler if an global Ajax Request started - */ - console.debug("°°° SiteView::on_ajax_start °°°"); - return this.start_spinner(); - }; - - SiteView.prototype.on_ajax_end = function(event) { - - /* - * Eventhandler if an global Ajax Request ended - */ - console.debug("°°° SiteView::on_ajax_end °°°"); - return this.stop_spinner(); - }; - - SiteView.prototype.on_ajax_error = function(event, jqxhr, settings, thrownError) { - - /* - * Eventhandler if an global Ajax Request error - */ - console.debug("°°° SiteView::on_ajax_error °°°"); - this.stop_spinner(); - return this.log("Error at " + settings.url + ": " + thrownError); - }; - return SiteView; })(); diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index d5289722c6..1eca70b819 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -1273,6 +1273,7 @@ class window.AnalysisRequestAdd # deactivate the button button = $("input[name=save_button]") button.prop "disabled": yes + button[0].value = _("Loading ...") on_ajax_end: => @@ -1284,6 +1285,7 @@ class window.AnalysisRequestAdd # reactivate the button button = $("input[name=save_button]") button.prop "disabled": no + button[0].value = _("Save") # Note: Context of callback bound to this object diff --git a/bika/lims/browser/js/coffee/bika.lims.site.coffee b/bika/lims/browser/js/coffee/bika.lims.site.coffee index fea2bfbc1d..441f01f0ec 100644 --- a/bika/lims/browser/js/coffee/bika.lims.site.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.site.coffee @@ -12,9 +12,6 @@ class window.SiteView jarn.i18n.loadCatalog 'senaite.core' @_ = window.jarn.i18n.MessageFactory("senaite.core") - # initialze the loading spinner - @init_spinner() - # initialze the client add overlay @init_client_add_overlay() @@ -78,10 +75,17 @@ class window.SiteView $("body").on "click", "a.service_info", @on_service_info_click - # handle Ajax events - $(document).on "ajaxStart", @on_ajax_start - $(document).on "ajaxStop", @on_ajax_end - $(document).on "ajaxError", @on_ajax_error + # Show loader on Ajax events + $(document).on + ajaxStart: -> + $("body").addClass "loading" + return + ajaxStop: -> + $("body").removeClass "loading" + return + ajaxError: -> + $("body").removeClass "loading" + return init_client_add_overlay: => @@ -108,25 +112,6 @@ class window.SiteView return - init_spinner: => - ### - * Initialize Spinner Overlay - ### - console.debug "SiteView::init_spinner" - - # unbind default Plone loader - $(document).unbind 'ajaxStart' - $(document).unbind 'ajaxStop' - $('#ajax-spinner').remove() - - # counter of active spinners - @counter = 0 - - # crate a spinner and append it to the body - @spinner = $("
") - @spinner.appendTo('body').hide() - - init_datepickers: => ### * Initialize date pickers @@ -364,42 +349,6 @@ class window.SiteView return - start_spinner: => - ### - * Start Spinner Overlay - ### - console.debug "SiteView::start_spinner" - - # increase the counter - @counter++ - - @timer = setTimeout (=> - if @counter > 0 - @spinner.show 'fast' - return - ), 500 - - return - - - stop_spinner: => - ### - * Stop Spinner Overlay - ### - console.debug "SiteView::stop_spinner" - - # decrease the counter - @counter-- - - if @counter < 0 - @counter = 0 - if @counter == 0 - clearTimeout @timer - @spinner.stop() - @spinner.hide() - return - - ### EVENT HANDLER ### on_date_range_start_change: (event) => @@ -626,35 +575,3 @@ class window.SiteView # workaround un-understandable overlay api $(el).click() - - - on_ajax_start: (event) => - ### - * Eventhandler if an global Ajax Request started - ### - console.debug "°°° SiteView::on_ajax_start °°°" - - # start the loading spinner - @start_spinner() - - - on_ajax_end: (event) => - ### - * Eventhandler if an global Ajax Request ended - ### - console.debug "°°° SiteView::on_ajax_end °°°" - - # stop the loading spinner - @stop_spinner() - - - on_ajax_error: (event, jqxhr, settings, thrownError) => - ### - * Eventhandler if an global Ajax Request error - ### - console.debug "°°° SiteView::on_ajax_error °°°" - - # stop the loading spinner - @stop_spinner() - - @log "Error at #{settings.url}: #{thrownError}" diff --git a/bika/lims/browser/templates/plone.app.controlpanel.overview.pt b/bika/lims/browser/templates/plone.app.controlpanel.overview.pt new file mode 100644 index 0000000000..51a69f1d5d --- /dev/null +++ b/bika/lims/browser/templates/plone.app.controlpanel.overview.pt @@ -0,0 +1,156 @@ + + + + + + +
+ +

Site Setup

+ +

+ Configuration area for Plone and add-on Products. +

+ +
+
+ Warning +
+
+ The site configuration is outdated and needs to be + upgraded. Please + + continue with the upgrade + . +
+
+ +
+
+ Warning +
+
+ You have not configured a mail host or a site 'From' + address, various features including contact forms, email + notification and password reset will not work. Go to the + + Mail control panel + + to fix this. +
+
+ +
+
+ Warning +
+
+ Warning: PIL is not installed properly, image scaling will not work. +
+
+ + +
+ +

Category

+ +
+ +
+ + +
+ +
+ No preference panels available. +
+ +
+ +
+
+ +
+

Version Overview

+ +
    +
  • Version
  • +
+ + +

+ You are running in "production mode". This is the preferred mode of + operation for a live Plone site, but means that some + configuration changes will not take effect until your server is + restarted or a product refreshed. If this is a development instance, + and you want to enable debug mode, stop the server, set 'debug-mode=on' + in your buildout.cfg, re-run bin/buildout and then restart the server + process. +

+ + +

+ You are running in "debug mode". This mode is intended for sites that + are under development. This allows many configuration changes to be + immediately visible, but will make your site run more slowly. To turn + off debug mode, stop the server, set 'debug-mode=off' in your + buildout.cfg, re-run bin/buildout and then restart the server + process. +

+
+ +
+ + + diff --git a/bika/lims/skins/bika/base_properties.props b/bika/lims/skins/bika/base_properties.props index bd0829b0da..3aab062243 100644 --- a/bika/lims/skins/bika/base_properties.props +++ b/bika/lims/skins/bika/base_properties.props @@ -1,4 +1,4 @@ -title:string=Plone's color, font, logo and border defaults +title:string=SENAITE Theme plone_skin:string=Plone Default logoName:string=logo.png diff --git a/bika/lims/skins/bika/main_template.pt b/bika/lims/skins/bika/main_template.pt new file mode 100644 index 0000000000..5b07cefa2c --- /dev/null +++ b/bika/lims/skins/bika/main_template.pt @@ -0,0 +1,206 @@ + + + + + Get the global cache headers located in global_cache_settings. + + + + + + + + + + + + +
+ + + + Various slots where you can insert elements in the header from a template. + + + + + + + + + + + + + + +
Loading...
+ +
+
+
+ +
+
+
+
+
+
+
+ +
+ + +
+ + + +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+
+ Status message +
+
+
+ +
+
+ +
+ + + Visual Header + + + + +
+ + + If you write a custom title always use +

for it +
+

+ Generic KSS Title. Is rendered with class="documentFirstHeading". +

+
+
+ + + + If you write a custom description always use +
for it +
+
+ Generic KSS Description. Is rendered with class="documentDescription". +
+
+ +
+
+ + Page body text + +
+
+ + +
+ + + + This slot is here for backwards compatibility only. + Don't use it in your custom templates. + +
+
+
+ +
+
+
+
+
+
+
+
+ + +
+ + + +
+
+
+ +
+
+ +
+
+
+ + + + diff --git a/bika/lims/skins/bika/ploneCustom.css.dtml b/bika/lims/skins/bika/ploneCustom.css.dtml index 09fad1c8fa..d1eb952060 100644 --- a/bika/lims/skins/bika/ploneCustom.css.dtml +++ b/bika/lims/skins/bika/ploneCustom.css.dtml @@ -1070,30 +1070,6 @@ div.LSResult { top:75%; } -#bika-spinner { - display:none; - background-color: #fff; - background-image: url(&dtml-portal_url;/spinner.gif) center center no-repeat; - position: fixed; - width: 45px; - height: 45px; - top: 50%; - left: 50%; - margin-left: -22px; /* -1 * image width / 2 */ - margin-top: -22px; /* -1 * image height / 2 */ - border:1px solid #eee; - border-radius:5px; - display: block; -} - -#bika-spinner img { - position: absolute; - top: 50%; - left: 50%; - margin-left: -8px; - margin-top: -8px; -} - #portal-alert input, #portal-alert a.button { background-color: #436976 !important;