diff options
Diffstat (limited to 'addons/web/static')
| -rw-r--r-- | addons/web/static/src/css/reset.min.css | 106 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/client_action.js | 200 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/qwebactionmanager.js | 140 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/report.editor.js | 109 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/subst.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/report/utils.js | 26 | ||||
| -rw-r--r-- | addons/web/static/src/less/layout_background.less | 68 | ||||
| -rw-r--r-- | addons/web/static/src/less/layout_boxed.less | 72 | ||||
| -rw-r--r-- | addons/web/static/src/less/layout_clean.less | 84 | ||||
| -rw-r--r-- | addons/web/static/src/less/report.editor.less | 26 | ||||
| -rw-r--r-- | addons/web/static/src/less/report.less | 35 | ||||
| -rw-r--r-- | addons/web/static/src/less/report_backend.less | 14 | ||||
| -rw-r--r-- | addons/web/static/src/xml/report.xml | 21 |
13 files changed, 915 insertions, 0 deletions
diff --git a/addons/web/static/src/css/reset.min.css b/addons/web/static/src/css/reset.min.css new file mode 100644 index 00000000000..ee21ad74e7d --- /dev/null +++ b/addons/web/static/src/css/reset.min.css @@ -0,0 +1,106 @@ +/* + +HTML5 CSS Reset +Based on Eric Meyer's CSS Reset +and html5doctor.com HTML5 Reset + +Copyright (c) 2011 736 Computing Services Limited +Released under the MIT license. http://opensource.736cs.com/licenses/mit + +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, i, center, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, audio, canvas, details, figcaption, +figure, footer, header, hgroup, mark, menu, meter, nav, +output, progress, section, summary, time, video { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} + +body { + line-height: 1; +} + +article, aside, dialog, figure, footer, header, +hgroup, nav, section, blockquote { + display: block; +} + +nav ul { + list-style: none; +} + +ol { + list-style: decimal; +} + +ul { + list-style: disc; +} + +ul ul { + list-style: circle; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +ins { + text-decoration: underline; +} + +del { + text-decoration: line-through; +} + +mark { + background: none; +} + +abbr[title], dfn[title] { + border-bottom:1px dotted #000; + cursor:help; +} + +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; +} + +input[type="submit"], input[type="button"], button { + padding: 0 !important; + margin: 0 !important; +} + +input, select, a img { + vertical-align: middle; +} diff --git a/addons/web/static/src/js/report/client_action.js b/addons/web/static/src/js/report/client_action.js new file mode 100644 index 00000000000..60265c0a49d --- /dev/null +++ b/addons/web/static/src/js/report/client_action.js @@ -0,0 +1,200 @@ +odoo.define('report.client_action', function (require) { +'use strict'; + +var core = require('web.core'); +var ControlPanelMixin = require('web.ControlPanelMixin'); +var session = require('web.session'); +var Widget = require('web.Widget'); +var utils = require('report.utils'); + +var QWeb = core.qweb; + + +var AUTHORIZED_MESSAGES = [ + 'report.editor:save_ok', + 'report.editor:discard_ok', + 'report:do_action', +]; + +var ReportAction = Widget.extend(ControlPanelMixin, { + + template: 'report.client_action', + + init: function (parent, action, options) { + this._super.apply(this, arguments); + + options = options || {}; + + this.action_manager = parent; + this.title = options.display_name || options.name; + + this.edit_mode_available = false; + this.in_edit_mode = false; + this.report_url = options.report_url; + + // Extra info that will be useful to build a qweb-pdf action. + this.report_name = options.report_name; + this.report_file = options.report_file; + this.data = options.data || {}; + this.context = options.context || {}; + }, + + start: function () { + var self = this; + this.set('title', this.title); + this.iframe = this.$('iframe')[0]; + return $.when(this._super.apply(this, arguments), session.is_bound).then(function () { + var web_base_url = session['web.base.url']; + var trusted_host = utils.get_host_from_url(web_base_url); + var trusted_protocol = utils.get_protocol_from_url(web_base_url); + self.trusted_origin = utils.build_origin(trusted_protocol, trusted_host); + + self.$buttons = $(QWeb.render('report.client_action.ControlButtons', {})); + self.$buttons.on('click', '.o_report_edit', self.on_click_edit); + self.$buttons.on('click', '.o_report_print', self.on_click_print); + self.$buttons.on('click', '.o_report_save', self.on_click_save); + self.$buttons.on('click', '.o_report_discard', self.on_click_discard); + + self._update_control_panel(); + + // Load the report in the iframe. Note that we use a relative URL. + self.iframe.src = self.report_url; + + // Once the iframe is loaded, check if we can edit the report. + self.iframe.onload = function () { + self._on_iframe_loaded(); + }; + }); + }, + + do_show: function () { + this._update_control_panel(); + return this._super.apply(this, arguments); + }, + + on_attach_callback: function () { + // Register now the postMessage event handler. We only want to listen to ~trusted + // messages and we can only filter them by their origin, so we chose to ignore the + // messages that do not come from `web.base.url`. + $(window).on('message', this, this.on_message_received); + }, + + on_detach_callback: function () { + $(window).off('message', this.on_message_received); + }, + + _on_iframe_loaded: function () { + var editable = $(this.iframe).contents().find('html').data('editable'); + if (editable === 1) { + this.edit_mode_available = true; + this._update_control_panel(); + } + }, + + _update_control_panel: function () { + this.update_control_panel({ + breadcrumbs: this.action_manager.get_breadcrumbs(), + cp_content: { + $buttons: this.$buttons, + }, + }); + this._update_control_panel_buttons(); + }, + + /** + * Helper allowing to toggle groups of buttons in the control panel + * according to the `this.in_edit_mode` flag. + */ + _update_control_panel_buttons: function () { + this.$buttons.filter('div.o_report_edit_mode').toggle(this.in_edit_mode); + this.$buttons.filter('div.o_report_no_edit_mode').toggle(! this.in_edit_mode); + this.$buttons.filter('div.o_edit_mode_available').toggle(core.debug && this.edit_mode_available && ! this.in_edit_mode); + }, + + /** + * Event handler of the message post. We only handle them if they're from + * `web.base.url` host and protocol and if they're part of `AUTHORIZED_MESSAGES`. + */ + on_message_received: function (ev) { + // Check the origin of the received message. + var message_origin = utils.build_origin(ev.originalEvent.source.location.protocol, ev.originalEvent.source.location.host); + if (message_origin === this.trusted_origin) { + + // Check the syntax of the received message. + var message = ev.originalEvent.data; + if (_.isObject(message)) { + message = message.message; + } + if (! _.isString(message) || (_.isString(message) && ! _.contains(AUTHORIZED_MESSAGES, message))) { + return; + } + + switch(message) { + case 'report.editor:save_ok': + // Reload the iframe in order to disable the editor. + this.iframe.src = this.report_url; + this.in_edit_mode = false; + this._update_control_panel_buttons(); + break; + case 'report.editor:discard_ok': + // Reload the iframe in order to disable the editor. + this.iframe.src = this.report_url; + this.in_edit_mode = false; + this._update_control_panel_buttons(); + break; + case 'report:do_action': + return this.do_action(ev.originalEvent.data.action); + default: + } + } + }, + + /** + * Helper allowing to send a message to the `this.el` iframe's window and + * seting the `targetOrigin` as `this.trusted_origin` (which is the + * `web.base.url` ir.config_parameter key) - in other word, only when using + * this method we only send the message to a trusted domain. + */ + _post_message: function (message) { + this.iframe.contentWindow.postMessage(message, this.trusted_origin); + }, + + on_click_edit: function () { + // We reload the iframe with a special query string to enable the editor. + if (this.report_url.indexOf('?') === -1) { + this.iframe.src = this.report_url + '?enable_editor=1'; + } else { + this.iframe.src = this.report_url + '&enable_editor=1'; + } + this.in_edit_mode = true; + this._update_control_panel_buttons(); + }, + + on_click_discard: function () { + this._post_message('report.editor:ask_discard'); + }, + + on_click_save: function () { + this._post_message('report.editor:ask_save'); + }, + + on_click_print: function () { + var action = { + 'type': 'ir.actions.report', + 'report_type': 'qweb-pdf', + 'report_name': this.report_name, + 'report_file': this.report_file, + 'data': this.data, + 'context': this.context, + 'display_name': this.title, + }; + return this.do_action(action); + }, + +}); + +core.action_registry.add('report.client_action', ReportAction); + +return ReportAction; + +}); diff --git a/addons/web/static/src/js/report/qwebactionmanager.js b/addons/web/static/src/js/report/qwebactionmanager.js new file mode 100644 index 00000000000..3c8fee859e3 --- /dev/null +++ b/addons/web/static/src/js/report/qwebactionmanager.js @@ -0,0 +1,140 @@ +odoo.define('report.report', function (require) { +'use strict'; + +var ActionManager = require('web.ActionManager'); +var core = require('web.core'); +var crash_manager = require('web.crash_manager'); +var framework = require('web.framework'); + +var _t = core._t; +var _lt = core._lt; + + +var wkhtmltopdf_state; + +// Messages that will be shown to the user (if needed). +var WKHTMLTOPDF_MESSAGES = { + 'install': _lt('Unable to find Wkhtmltopdf on this \nsystem. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">\nwkhtmltopdf.org</a>'), + 'workers': _lt('You need to start OpenERP with at least two \nworkers to print a pdf version of the reports.'), + 'upgrade': _lt('You should upgrade your version of\nWkhtmltopdf to at least 0.12.0 in order to get a correct display of headers and footers as well as\nsupport for table-breaking between pages.<br><br><a href="http://wkhtmltopdf.org/" \ntarget="_blank">wkhtmltopdf.org</a>'), + 'broken': _lt('Your installation of Wkhtmltopdf seems to be broken. The report will be shown in html.<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>') +}; + +var trigger_download = function (session, response, c, action, options) { + session.get_file({ + url: '/report/download', + data: {data: JSON.stringify(response)}, + complete: framework.unblockUI, + error: c.rpc_error.bind(c), + success: function () { + if (action && options && !action.dialog) { + options.on_close(); + } + }, + }); +}; + +/** + * This helper will generate an object containing the report's url (as value) + * for every qweb-type we support (as key). It's convenient because we may want + * to use another report's type at some point (for example, when `qweb-pdf` is + * not available). + */ +var make_report_url = function (action) { + var report_urls = { + 'qweb-html': '/report/html/' + action.report_name, + 'qweb-pdf': '/report/pdf/' + action.report_name, + }; + // We may have to build a query string with `action.data`. It's the place + // were report's using a wizard to customize the output traditionally put + // their options. + if (_.isUndefined(action.data) || _.isNull(action.data) || (_.isObject(action.data) && _.isEmpty(action.data))) { + if (action.context.active_ids) { + var active_ids_path = '/' + action.context.active_ids.join(','); + // Update the report's type - report's url mapping. + report_urls = _.mapObject(report_urls, function (value, key) { + return value += active_ids_path; + }); + } + } else { + var serialized_options_path = '?options=' + encodeURIComponent(JSON.stringify(action.data)); + serialized_options_path += '&context=' + encodeURIComponent(JSON.stringify(action.context)); + // Update the report's type - report's url mapping. + report_urls = _.mapObject(report_urls, function (value, key) { + return value += serialized_options_path; + }); + } + return report_urls; +}; + +ActionManager.include({ + ir_actions_report: function (action, options) { + var self = this; + action = _.clone(action); + + var report_urls = make_report_url(action); + + if (action.report_type === 'qweb-html') { + var client_action_options = _.extend({}, options, { + report_url: report_urls['qweb-html'], + report_name: action.report_name, + report_file: action.report_file, + data: action.data, + context: action.context, + name: action.name, + display_name: action.display_name, + }); + return this.do_action('report.client_action', client_action_options); + } else if (action.report_type === 'qweb-pdf') { + framework.blockUI(); + // Before doing anything, we check the state of wkhtmltopdf on the server. + (wkhtmltopdf_state = wkhtmltopdf_state || this._rpc({route: '/report/check_wkhtmltopdf'})).then(function (state) { + // Display a notification to the user according to wkhtmltopdf's state. + if (WKHTMLTOPDF_MESSAGES[state]) { + self.do_notify(_t('Report'), WKHTMLTOPDF_MESSAGES[state], true); + } + + if (state === 'upgrade' || state === 'ok') { + // Trigger the download of the PDF report. + var response; + var c = crash_manager; + + var treated_actions = []; + var current_action = action; + do { + report_urls = make_report_url(current_action); + response = [ + report_urls['qweb-pdf'], + action.report_type, //The 'root' report is considered the maine one, so we use its type for all the others. + ]; + trigger_download(self.getSession(), response, c, current_action, options); + + treated_actions.push(current_action); + current_action = current_action.next_report_to_generate; + } while (current_action && !_.contains(treated_actions, current_action)); + //Second part of the condition for security reasons (avoids infinite loop possibilty). + + return; + + } else { + // Open the report in the client action if generating the PDF is not possible. + var client_action_options = _.extend({}, options, { + report_url: report_urls['qweb-html'], + report_name: action.report_name, + report_file: action.report_file, + data: action.data, + context: action.context, + name: action.name, + display_name: action.display_name, + }); + framework.unblockUI(); + return self.do_action('report.client_action', client_action_options); + } + }); + } else { + return self._super(action, options); + } + } +}); + +}); diff --git a/addons/web/static/src/js/report/report.editor.js b/addons/web/static/src/js/report/report.editor.js new file mode 100644 index 00000000000..57942f9b79c --- /dev/null +++ b/addons/web/static/src/js/report/report.editor.js @@ -0,0 +1,109 @@ +odoo.define('report.editor', function (require) { +'use strict'; + +var ajax = require('web.ajax'); +var core = require('web.core'); +var utils = require('report.utils'); +var editor = require('web_editor.editor'); +var options = require('web_editor.snippets.options'); + +var web_base_url = $('html').attr('web-base-url'); +var trusted_host = utils.get_host_from_url(web_base_url); +var trusted_protocol = utils.get_protocol_from_url(web_base_url); +var trusted_origin = utils.build_origin(trusted_protocol, trusted_host); + +ajax.loadXML('/web/static/src/xml/base_common.xml', core.qweb); + +// Patch the editor's behavior when it is launched inside an iframe. +if (window.self !== window.top) { + $(document.body).addClass('o_in_iframe'); // in order to apply css rules + + // As `reload` is called after `save` and `cancel`, we nullify this function + // if the editor in order to be able to chain some deferred to `save` and + // `cancel`. + editor.reload = function () { + return $.when(); + }; + + // And now we chain some deferred to `save` and `cancel` in order to inform + // the report's client action that the actions are done. + editor.Class.include({ + save: function () { + return this._super.apply(this, arguments).then(function () { + window.parent.postMessage('report.editor:save_ok', trusted_origin); + }); + }, + cancel: function () { + return this._super.apply(this, arguments).then(function () { + window.parent.postMessage('report.editor:discard_ok', trusted_origin); + }); + }, + }); + + // postMessage logic. + var AUTHORIZED_MESSAGES = [ + 'report.editor:ask_discard', + 'report.editor:ask_save', + ]; + + window.addEventListener('message', function (ev) { + // Check the origin of the received message. + var message_origin = utils.build_origin(ev.source.location.protocol, ev.source.location.host); + if (message_origin === trusted_origin) { + // Check the syntax of the received message. + var message = ev.data; + if (! _.isString(message) || (_.isString(message) && ! _.contains(AUTHORIZED_MESSAGES, message))) { + return; + } + + switch(message) { + case 'report.editor:ask_save': + editor.editor_bar.save(); + break; + case 'report.editor:ask_discard': + editor.editor_bar.cancel(); + break; + default: + } + } + }, false); + + // Allow to send commands to the webclient when the editor is disabled. + if (window.location.search.indexOf('enable_editor') === -1) { + // `do_action` command + $('[res-id][res-model][view-type]') + .wrap('<a/>') + .attr('href', '#') + .on('click', function (ev) { + ev.preventDefault(); + var action = { + 'type': 'ir.actions.act_window', + 'view_type': $(this).attr('view-type'), + 'view_mode': $(this).attr('view-mode') || $(this).attr('view-type'), + 'res_id': Number($(this).attr('res-id')), + 'res_model': $(this).attr('res-model'), + 'views': [[$(this).attr('view-id') || false, $(this).attr('view-type')]], + }; + window.parent.postMessage({ + 'message': 'report:do_action', + 'action': action, + }, trusted_origin); + }); + } +} + +options.registry.many2one.include({ + select_record: function (li) { + var self = this; + this._super(li); + if (this.$target.data('oe-field') === "partner_id") { + var $img = $('.header .row img:first'); + var css = window.getComputedStyle($img[0]); + $img.css("max-height", css.height+'px'); + $img.attr("src", "/web/image/res.partner/"+self.ID+"/image"); + setTimeout(function () { $img.removeClass('o_dirty'); },0); + } + } +}); + +}); diff --git a/addons/web/static/src/js/report/subst.js b/addons/web/static/src/js/report/subst.js new file mode 100644 index 00000000000..076a5ff208a --- /dev/null +++ b/addons/web/static/src/js/report/subst.js @@ -0,0 +1,14 @@ +function subst() { + var vars = {}; + var x = document.location.search.substring(1).split('&'); + for (var i in x) { + var z = x[i].split('=', 2); + vars[z[0]] = unescape(z[1]); + } + var x=['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection']; + for (var i in x) { + var y = document.getElementsByClassName(x[i]); + for (var j=0; j<y.length; ++j) + y[j].textContent = vars[x[i]]; + } +} diff --git a/addons/web/static/src/js/report/utils.js b/addons/web/static/src/js/report/utils.js new file mode 100644 index 00000000000..64776efb4db --- /dev/null +++ b/addons/web/static/src/js/report/utils.js @@ -0,0 +1,26 @@ +odoo.define('report.utils', function (require) { +'use strict'; + +function get_protocol_from_url (url) { + var a = document.createElement('a'); + a.href = url; + return a.protocol; +} + +function get_host_from_url (url) { + var a = document.createElement('a'); + a.href = url; + return a.host; +} + +function build_origin (protocol, host) { + return protocol + '//' + host; +} + +return { + 'get_protocol_from_url': get_protocol_from_url, + 'get_host_from_url': get_host_from_url, + 'build_origin': build_origin, +}; + +}); diff --git a/addons/web/static/src/less/layout_background.less b/addons/web/static/src/less/layout_background.less new file mode 100644 index 00000000000..f00fec6b33e --- /dev/null +++ b/addons/web/static/src/less/layout_background.less @@ -0,0 +1,68 @@ +.o_background_footer, .o_background_header, .o_report_layout_background { + font-family: 'Lato'; + color: @gray; +} +.o_background_header { + border-bottom: 1px solid @gray-lighter; + .company_address { + min-width: 300px; + } + img { + max-height: 96px; + max-width: 300px; + margin-right: 16px; + } + h3 { + color: @odoo-brand-primary; + font-weight: 700; + max-width: 300px; + } +} +.o_background_footer { + ul { + border-top: 1px solid @odoo-brand-primary; + border-bottom: 1px solid @odoo-brand-primary; + padding: 4px 0; + margin: 0 0 4px 0; + li { + color: @odoo-brand-primary; + } + } +} +.o_report_layout_background { + background-image: url(/report/static/src/img/bg_background_template.jpg); + background-size: cover; + background-position: bottom center; + background-repeat: no-repeat; + min-height: 920px; + thead tr th, h2 { + color: @odoo-brand-primary; + } + tbody { + color: @gray; + tr:nth-child(odd) { + background-color: rgba(184, 171, 180, 0.2); + } + tr td { + border-top: none; + } + } + /*Total table*/ + .row > .col-xs-4 > table { + tr:nth-child(odd) { + background-color: transparent !important; + } + tr:first-child td { + /*we could get rid of this rule if .border-black td would be terminated in report.less*/ + border-top: none !important; + } + tr:last-child td { + color: @odoo-brand-primary; + } + } + #total { + table tr:last-child td { + border-bottom: 1px solid black; + } + } +} diff --git a/addons/web/static/src/less/layout_boxed.less b/addons/web/static/src/less/layout_boxed.less new file mode 100644 index 00000000000..a81e09f347b --- /dev/null +++ b/addons/web/static/src/less/layout_boxed.less @@ -0,0 +1,72 @@ +.o_boxed_footer, .o_boxed_header, .o_report_layout_boxed { + font-family: 'Lato'; + color: @gray; + font-size: 12px; +} +.o_boxed_header { + border-bottom: 1px solid @gray-lighter; + img { + max-height: 100px; + } + h4 { + color: @o-we-color-text-normal; + font-weight: 700; + text-transform: uppercase; + } +} +.o_boxed_footer { + border-top: 3px solid @gray; + ul { + margin: 4px 0; + } +} +.o_report_layout_boxed { + // This is not a general rule since the id is not used in all the reports, + // we should make a general structure for report body to use it everywhere + #informations { + border: 1px solid @gray; + padding-top: 10px; + margin: 0; + } + > h2 { + text-transform: uppercase; + } + table { + border: 1px solid @gray; + thead { + tr th { + border-bottom: 3px solid @gray !important; + text-transform: uppercase; + } + } + tbody { + color: @gray; + tr { + td { + border-top: none; + border-right: 1px solid @gray; + } + td:last-child { + background-color: @gray-lighter; + } + } + } + } + /*Total table*/ + .row > .col-xs-4 > table { + td { + border-right: none; + } + tr:last-child td, tr:first-child td { + /*we could get rid of this rule if .border-black td would be terminated in report.less*/ + border-top: none !important; + } + tr:first-child td { + background-color: transparent; + } + tr:last-child td { + background-color: @gray; + color: #fff; + } + } +} diff --git a/addons/web/static/src/less/layout_clean.less b/addons/web/static/src/less/layout_clean.less new file mode 100644 index 00000000000..85e9c2d8491 --- /dev/null +++ b/addons/web/static/src/less/layout_clean.less @@ -0,0 +1,84 @@ +.o_clean_footer, .o_clean_header, .o_report_layout_clean { + font-family: 'Lato'; + color: #000; +} +.o_clean_header { + img { + max-height: 90px; + max-width: 300px; + } +} +.o_clean_footer { + margin: 0 3px; + border-top: 3px solid @odoo-brand-primary; + h4 { + color: @odoo-brand-primary; + font-weight: bolder; + } + .pagenumber { + width: 35px; + height: 35px; + border: 3px solid @odoo-brand-primary; + background-color: @odoo-brand-optional; + color: white; + padding-top: 6px; + text-align: center; + } +} +.o_report_layout_clean { + h1, h2, h3 { + color: @odoo-brand-optional; + font-weight: bolder; + } + strong { + color: @odoo-brand-primary; + } + table { + thead { + color: @odoo-brand-primary; + tr th { + text-align: left !important; + border-top: 3px solid @odoo-brand-primary !important; + text-transform: uppercase; + } + tr th:last-child { + text-align: right !important; + } + tr th:first-child { + width: 48%; + } + } + tbody { + color: #000; + tr:first-child td { + border-top: none; + } + tr:last-child td { + border-bottom: 3px solid @odoo-brand-primary; + } + tr { + td { + padding: 15px 5px; + text-align: left !important; + } + td:last-child { + text-align: right !important; + } + } + } + } + /*Total table*/ + .row > .col-xs-4 { + width: 43%; + margin-top: -20px; + > table { + tr:last-child td { + border-top: 1px solid @gray-lighter !important; + } + tr:first-child td { + /*we could get rid of this rule if .border-black td would be terminated in report.less*/ + border-top: none !important; + } + } + } +} diff --git a/addons/web/static/src/less/report.editor.less b/addons/web/static/src/less/report.editor.less new file mode 100644 index 00000000000..206ddb87037 --- /dev/null +++ b/addons/web/static/src/less/report.editor.less @@ -0,0 +1,26 @@ +.o_editable { + &:not(:empty), &[data-oe-type] { + &[date-oe-model="ir.ui.view"] { + &, th, td { + &:hover { + -moz-box-shadow: #DDCC33 0 0 5px 2px inset; + -webkit-box-shadow: #DDCC33 0 0 5px 2px inset; + box-shadow: #DDCC33 0 0 5px 2px inset; + } + } + } + } +} + +body.o_in_iframe { + margin-top: 40px!important; + div.o_css_editor { + > form { + display: none; + } + } + + .header, .footer { + display: none; + } +} diff --git a/addons/web/static/src/less/report.less b/addons/web/static/src/less/report.less new file mode 100644 index 00000000000..f34a2393fbe --- /dev/null +++ b/addons/web/static/src/less/report.less @@ -0,0 +1,35 @@ +html, body { + height: 100%; +} + +body { + color: #000 !important; + word-wrap: break-word; +} +.border-black td { + border-top: 1px solid black !important; +} + +/* See https://github.com/wkhtmltopdf/wkhtmltopdf/issues/1524 */ +.table-condensed { + > thead > tr > th { + border-bottom: none !important; + } + > tbody > tr { + page-break-inside: avoid !important; + border-top: none !important; + } +} +.zero_min_height { + min-height: 0px !important; +} + +/* To avoid broken snippets in report rendering */ +.jumbotron, .panel, .carousel, section{ + page-break-inside: avoid; +} + +/* Wkhtmltopdf doesn't support very well the media-print CSS (depends on the version) */ +.hidden-print{ + display: none; +} diff --git a/addons/web/static/src/less/report_backend.less b/addons/web/static/src/less/report_backend.less new file mode 100644 index 00000000000..62252c2d9f7 --- /dev/null +++ b/addons/web/static/src/less/report_backend.less @@ -0,0 +1,14 @@ +.o_report_iframe { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: none; +} + +.o_report_no_edit_mode, .o_edit_mode_available, .o_report_edit_mode { + display: inline-block; +} diff --git a/addons/web/static/src/xml/report.xml b/addons/web/static/src/xml/report.xml new file mode 100644 index 00000000000..c578add8ce9 --- /dev/null +++ b/addons/web/static/src/xml/report.xml @@ -0,0 +1,21 @@ +<template> + + <!-- Main layout (iframe) --> + <div t-name="report.client_action"> + <iframe class="o_report_iframe"/> + </div> + + <!-- Buttons of the Control Panel --> + <t t-name="report.client_action.ControlButtons"> + <div class="o_report_no_edit_mode"> + <button type="button" class="btn btn-primary btn-sm o_report_print" title="Print">Print</button> + </div> + <div class="o_edit_mode_available"> + <button type="button" class="btn btn-default btn-sm o_report_edit" title="Edit">Edit</button> + </div> + <div class="o_report_edit_mode"> + <button type="button" class="btn btn-primary btn-sm o_report_save" title="Save">Save</button> + <button type="button" class="btn btn-default btn-sm o_report_discard" title="Discard">Discard</button> + </div> + </t> +</template>
\ No newline at end of file |
