diff --git a/CHANGES.rst b/CHANGES.rst index 4851a0d7ac..6a18fa9aa6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ Changelog **Added** +- #1534 Integrate browser resources from senaite.lims - #1529 Moved contentmenu provider into core - #1523 Moved Installation Screens into core - #1520 JavaScripts/CSS Integration and Cleanup diff --git a/bika/lims/monkey/__init__.py b/bika/lims/monkey/__init__.py index b54d2c0984..41a69c01d3 100644 --- a/bika/lims/monkey/__init__.py +++ b/bika/lims/monkey/__init__.py @@ -17,3 +17,5 @@ # # Copyright 2018-2020 by it's authors. # Some rights reserved, see README and LICENSE. + +import add_senaite_site # noqa diff --git a/bika/lims/monkey/add_senaite_site.py b/bika/lims/monkey/add_senaite_site.py new file mode 100644 index 0000000000..c8a9a35c31 --- /dev/null +++ b/bika/lims/monkey/add_senaite_site.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SENAITE.CORE. +# +# SENAITE.CORE 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 OFS.ObjectManager import ObjectManager + +ADD_SENAITE_SITE = """ + + + + + + +
+ + +
+
+""" + +main = ObjectManager.manage_main +orig = main.read() +pos = orig.find('') + + +# Add in our button html at the right position +new = orig[:pos] + ADD_SENAITE_SITE + orig[pos:] + +# Modify the manage_main +main.edited_source = new +main._v_cooked = main.cook() diff --git a/bika/lims/monkey/configure.zcml b/bika/lims/monkey/configure.zcml index d071918756..9bc64bdfba 100644 --- a/bika/lims/monkey/configure.zcml +++ b/bika/lims/monkey/configure.zcml @@ -8,6 +8,13 @@ xmlns:monkey="http://namespaces.plone.org/monkey" i18n_domain="senaite.core"> + + + '); + + content.data('pbo', pbo); + + // if a width option is specified, set it on the overlay div, + // computing against the window width if a % was specified. + if (pbw) { + if (pbw.indexOf('%') > 0) { + content.width(parseInt(pbw, 10) / 100 * $(window).width()); + } else { + content.width(pbw); + } + } + + // add the target element at the end of the body. + content.appendTo($("body")); + + return content; + }; + + + /****** + pb.image_click + click handler for image loads + ******/ + pb.image_click = function (event) { + var ethis, content, api, img, el, pbo; + + ethis = $(this); + pbo = ethis.data('pbo'); + + // find target container + content = $(ethis.attr('rel')); + if (!content.length) { + content = pb.create_content_div(pbo); + content.overlay(pbo.config); + } + api = content.overlay(); + + // is the image loaded yet? + if (content.find('img').length === 0) { + // load the image. + if (pbo.src) { + pb.spinner.show(); + + // create the image and stuff it + // into our target + img = new Image(); + img.src = pbo.src; + el = $(img); + content.append(el.addClass('pb-image')); + + // Now, we'll cause the overlay to + // load when the image is loaded. + el.load(function () { + pb.spinner.hide(); + api.load(); + }); + + } + } else { + api.load(); + } + + return false; + }; + + + /****** + pb.fi_focus + First-input focus inside $ selection. + ******/ + pb.fi_focus = function (jqo) { + if (! jqo.find("form div.error :input:first").focus().length) { + jqo.find("form :input:visible:first").focus(); + } + }; + + + /****** + pb.ajax_error_recover + jQuery's ajax load function does not load error responses. + This routine returns the cooked error response. + ******/ + pb.ajax_error_recover = function (responseText, selector) { + var tcontent = $('
').append( + responseText.replace(//gi, "")); + return selector ? tcontent.find(selector) : tcontent; + }; + + + /****** + pb.add_ajax_load + Adds a hidden ajax_load input to form + ******/ + pb.add_ajax_load = function (form) { + if (form.find('input[name=ajax_load]').length === 0) { + form.prepend($('')); + } + }; + + /****** + pb.prep_ajax_form + Set up form with ajaxForm, including success and error handlers. + ******/ + pb.prep_ajax_form = function (form) { + var ajax_parent = form.closest('.pb-ajax'), + data_parent = ajax_parent.closest('.overlay-ajax'), + pbo = data_parent.data('pbo'), + formtarget = pbo.formselector, + closeselector = pbo.closeselector, + beforepost = pbo.beforepost, + afterpost = pbo.afterpost, + noform = pbo.noform, + api = data_parent.overlay(), + selector = pbo.selector, + options = {}; + + options.beforeSerialize = function () { + pb.spinner.show(); + }; + + if (beforepost) { + options.beforeSubmit = function (arr, form, options) { + return beforepost(form, arr, options); + }; + } + options.success = function (responseText, statusText, xhr, form) { + $(document).trigger('formOverlayStart', [this, responseText, statusText, xhr, form]); + // success comes in many forms, some of which are errors; + // + + var el, myform, success, target, scripts = [], filteredResponseText; + + success = statusText === "success" || statusText === "notmodified"; + + if (! success) { + // The responseText parameter is actually xhr + responseText = responseText.responseText; + } + // strip inline script tags + filteredResponseText = responseText.replace(//gi, ""); + + // create a div containing the optionally filtered response + el = $('
').append( + selector ? + // a lesson learned from the jQuery source: $(responseText) + // will not work well unless responseText is well-formed; + // appending to a div is more robust, and automagically + // removes the html/head/body outer tagging. + $('
').append(filteredResponseText).find(selector) + : + filteredResponseText + ); + + // afterpost callback + if (success && afterpost) { + afterpost(el, data_parent); + } + + myform = el.find(formtarget); + if (success && myform.length) { + ajax_parent.empty().append(el); + + // execute inline scripts + + // try { + // // jQuery 1.7 + // $.buildFragment([responseText], [document], scripts); + // } catch (e) { + // // jQuery 1.9 + // $.buildFragment([responseText], document, scripts); + // } + + if (scripts.length) { + $.each(scripts, function() { + $.globalEval( this.text || this.textContent || this.innerHTML || "" ); + }); + } + + // This may be a complex form. + if ($.fn.ploneTabInit) { + el.ploneTabInit(); + } + pb.fi_focus(ajax_parent); + + pb.add_ajax_load(myform); + // attach submit handler with the same options + myform.ajaxForm(options); + + // attach close to element id'd by closeselector + if (closeselector) { + el.find(closeselector).click(function (event) { + api.close(); + return false; + }); + } + $(document).trigger('formOverlayLoadSuccess', [this, myform, api, pb, ajax_parent]); + } else { + // there's no form in our new content or there's been an error + if (success) { + if (typeof noform === "function") { + // get action from callback + noform = noform(el, pbo); + } + } else { + noform = statusText; + } + + + switch (noform) { + case 'close': + api.close(); + break; + case 'reload': + api.close(); + pb.spinner.show(); + // location.reload results in a repost + // dialog in some browsers; very unlikely to + // be what we want. + location.replace(location.href); + break; + case 'redirect': + api.close(); + pb.spinner.show(); + target = pbo.redirect; + if (typeof target === "function") { + // get target from callback + target = target(el, responseText, pbo); + } + location.replace(target); + break; + default: + if (el.children()) { + // show what we've got + ajax_parent.empty().append(el); + } else { + api.close(); + } + break; + } + $(document).trigger('formOverlayLoadFailure', [this, myform, api, pb, ajax_parent, noform]); + } + pb.spinner.hide(); + }; + // error and success callbacks are the same + options.error = options.success; + + pb.add_ajax_load(form); + form.ajaxForm(options); + }; + + + /****** + pb.ajax_click + Click handler for ajax sources. The job of this routine + is to do the ajax load of the overlay element, then + call the JQT overlay loader. + ******/ + pb.ajax_click = function (event) { + var ethis = $(this), + pbo, + content, + api, + src, + el, + selector, + formtarget, + closeselector, + sep, + scripts = [], + e; + + e = $.Event(); + e.type = "beforeAjaxClickHandled"; + $(document).trigger(e, [this, event]); + if (e.isDefaultPrevented()) { return false; } + + pbo = ethis.data('pbo'); + + content = pb.create_content_div(pbo, ethis); + // pbo.config.top = $(window).height() * 0.1 - ethis.offsetParent().offset().top; + content.overlay(pbo.config, ethis); + api = content.overlay(); + src = pbo.src; + selector = pbo.selector; + formtarget = pbo.formselector; + closeselector = pbo.closeselector; + + pb.spinner.show(); + + // prevent double click warning for this form + $(this).find("input.submitting").removeClass('submitting'); + + el = $('div.pb-ajax', content); + if (el.length === 0) { + el = $('
'); + content.append(el); + } + if (api.getConf().fixed) { + // don't let it be over 75% of the viewport's height + el.css('max-height', Math.floor($(window).height() * 0.75)); + } + + // affix a random query argument to prevent + // loading from browser cache + sep = (src.indexOf('?') >= 0) ? '&': '?'; + src += sep + "ajax_load=" + (new Date().getTime()); + + // add selector, if any + if (selector) { + src += ' ' + selector; + } + + // set up callback to be used whenever new contents are loaded + // into the overlay, to prepare links and forms to stay within + // the overlay + el[0].handle_load_inside_overlay = function(responseText, errorText) { + var ele, target; + ele = $(this); + + if (errorText === 'error') { + ele.append(pb.ajax_error_recover(responseText, selector)); + } else if (!responseText.length) { + ele.append(ajax_noresponse_message || 'No response from server.'); + } + + // a non-semantic div here will make sure we can + // do enough formatting. + ele.wrapInner('
'); + + // add the submit handler if we've a formtarget + if (formtarget) { + target = ele.find(formtarget); + if (target.length > 0) { + pb.prep_ajax_form(target); + } + } + + // if a closeselector has been specified, tie it to the overlay's + // close method via closure + if (closeselector) { + ele.find(closeselector).click(function (event) { + api.close(); + return false; + }); + } + + // execute inline scripts + // try { + // // jQuery 1.7 + // $.buildFragment([responseText], [document], scripts); + // } catch (e) { + // // jQuery 1.9 + // debugger + // $.buildFragment([responseText], document, scripts); + // } + + if (scripts.length) { + $.each(scripts, function() { + $.globalEval( this.text || this.textContent || this.innerHTML || "" ); + }); + } + + // This may be a complex form. + if ($.fn.ploneTabInit) { + ele.ploneTabInit(); + } + + // remove element on close so that it doesn't congest the DOM + api.onClose = function () { + content.remove(); + }; + $(document).trigger('loadInsideOverlay', [this, responseText, errorText, api]); + }; + + // and load the div + var data = null; + var form = $(pbo.source.context); + var method = form.attr('method'); + if (method && method.toLowerCase() == 'post') { + data = form.serializeArray(); + } + // load will do a POST instead of GET if data is not null + el.load(src, data, function (responseText, errorText) { + // post-process the overlay contents + el[0].handle_load_inside_overlay.apply(this, [responseText, errorText]); + + // Now, it's all ready to display; hide the + // spinner and call JQT overlay load. + pb.spinner.hide(); + api.load(); + + return true; + }); + + // don't do the default action + return false; + }; + + + /****** + pb.iframe + onBeforeLoad handler for iframe overlays. + + Note that the spinner is handled a little differently + so that we can keep it displayed while the iframe's + content is loading. + ******/ + pb.iframe = function () { + var content, pbo; + + pb.spinner.show(); + + content = this.getOverlay(); + pbo = this.getTrigger().data('pbo'); + + if (content.find('iframe').length === 0 && pbo.src) { + content.append( + ' +
+ + + + +
+
+ + Forgot your password? + +

+ If you have forgotten your password, + + we can send you a new one. +

+
+ +
+ + New user? + + +

+ If you do not have an account here, head over to the + + + registration form. +

+ +
+
+ + +
+ + + + diff --git a/bika/lims/skins/bika/login_form.cpt.metadata b/bika/lims/skins/bika/login_form.cpt.metadata new file mode 100644 index 0000000000..1c6a68f5cf --- /dev/null +++ b/bika/lims/skins/bika/login_form.cpt.metadata @@ -0,0 +1,11 @@ +[default] +title=Sign in +border=None + +[validators] +validators=login_form_validate + +[actions] +action.success=traverse_to:string:logged_in +action.failure=redirect_to:string:login_form +action.failure_page=traverse_to:string:login_failed diff --git a/bika/lims/skins/bika/logo.png b/bika/lims/skins/bika/logo.png index 20cd2641d3..31569bd8fb 100644 Binary files a/bika/lims/skins/bika/logo.png and b/bika/lims/skins/bika/logo.png differ diff --git a/bika/lims/skins/bika/logo_print.png b/bika/lims/skins/bika/logo_print.png index 85a6721738..d0987df385 100644 Binary files a/bika/lims/skins/bika/logo_print.png and b/bika/lims/skins/bika/logo_print.png differ