diff --git a/addons/survey/__manifest__.py b/addons/survey/__manifest__.py index cb8f221dd49..abadb891fd5 100644 --- a/addons/survey/__manifest__.py +++ b/addons/survey/__manifest__.py @@ -61,6 +61,7 @@ sent mails with personal token for the invitation of the survey. 'assets': { 'survey.survey_assets': [ 'web/static/lib/Chart/Chart.js', + 'survey/static/src/js/survey_image_zoomer.js', 'survey/static/src/js/survey_quick_access.js', 'survey/static/src/js/survey_timer.js', 'survey/static/src/js/survey_breadcrumb.js', diff --git a/addons/survey/models/survey_question.py b/addons/survey/models/survey_question.py index 0d74c108285..bf91546bcea 100644 --- a/addons/survey/models/survey_question.py +++ b/addons/survey/models/survey_question.py @@ -600,7 +600,8 @@ class SurveyQuestionAnswer(models.Model): scoring_type = fields.Selection(related='question_id.scoring_type') # answer related fields value = fields.Char('Suggested value', translate=True, required=True) - value_image = fields.Image('Image', max_width=256, max_height=256) + value_image = fields.Image('Image', max_width=1024, max_height=1024) + value_image_filename = fields.Char('Image Filename') is_correct = fields.Boolean('Correct') answer_score = fields.Float('Score', help="A positive score indicates a correct choice; a negative or null score indicates a wrong answer") diff --git a/addons/survey/static/src/js/survey_form.js b/addons/survey/static/src/js/survey_form.js index dd44314d636..ef8dcbfb235 100644 --- a/addons/survey/static/src/js/survey_form.js +++ b/addons/survey/static/src/js/survey_form.js @@ -11,6 +11,7 @@ var dom = require('web.dom'); var utils = require('web.utils'); var SurveyPreloadImageMixin = require('survey.preload_image_mixin'); +const { SurveyImageZoomer } = require("@survey/js/survey_image_zoomer"); var _t = core._t; var isMac = navigator.platform.toUpperCase().includes('MAC'); @@ -22,6 +23,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa 'click .o_survey_matrix_btn': '_onMatrixBtnClick', 'click input[type="radio"]': '_onRadioChoiceClick', 'click button[type="submit"]': '_onSubmit', + 'click .o_survey_choice_img img': '_onChoiceImgClick', 'focusin .form-control': '_updateEnterButtonText', 'focusout .form-control': '_updateEnterButtonText' }, @@ -43,6 +45,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa self.options = self.$target.find('form').data(); self.readonly = self.options.readonly; self.selectedAnswers = self.options.selectedAnswers; + self.imgZoomer = false; // Add Survey cookie to retrieve the survey if you quit the page and restart the survey. if (!utils.get_cookie('survey_' + self.options.surveyToken)) { @@ -108,6 +111,10 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa if (this.$('fieldset[disabled="disabled"]').length !== 0) { return; } + // Disable all navigation keys when zoom modal is open, except the ESC. + if ((this.imgZoomer && !this.imgZoomer.isDestroyed()) && keyCode !== 27) { + return; + } var letter = String.fromCharCode(keyCode).toUpperCase(); @@ -255,6 +262,22 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa } }, + /** + * Called when an image on an answer in multi-answers question is clicked. + * Starts a widget opening a dialog to display the now zoomable image. + * this.imgZoomer is the zoomer widget linked to the survey form, if any. + * + * @private + * @param {Event} ev + */ + _onChoiceImgClick: function (ev) { + ev.preventDefault(); + this.imgZoomer = new SurveyImageZoomer({ + sourceImage: $(ev.currentTarget).attr('src') + }); + this.imgZoomer.appendTo(document.body); + }, + /** * Invert the related input's "checked" property. * This will tick or untick the option (based on the previous state). diff --git a/addons/survey/static/src/js/survey_image_zoomer.js b/addons/survey/static/src/js/survey_image_zoomer.js new file mode 100644 index 00000000000..5a7b446a7ac --- /dev/null +++ b/addons/survey/static/src/js/survey_image_zoomer.js @@ -0,0 +1,112 @@ +/** @odoo-module */ + +import publicWidget from 'web.public.widget'; + +export const SurveyImageZoomer = publicWidget.Widget.extend({ + template: 'survey.survey_image_zoomer', + xmlDependencies: ['/survey/static/src/xml/survey_image_zoomer_templates.xml'], + events: { + 'wheel .o_survey_img_zoom_image': '_onImgScroll', + 'click': '_onZoomerClick', + 'click .o_survey_img_zoom_in_btn': '_onZoomInClick', + 'click .o_survey_img_zoom_out_btn': '_onZoomOutClick', + }, + /** + * @override + */ + init(params) { + this.zoomImageScale = 1; + // The image is needed to render the template survey_image_zoom. + this.sourceImage = params.sourceImage; + this._super(... arguments); + }, + /** + * Open a transparent modal displaying the survey choice image. + * @override + */ + async start() { + const superResult = await this._super(...arguments); + // Prevent having hidden modal in the view. + this.$el.on('hidden.bs.modal', () => this.destroy()); + this.$el.modal('show'); + return superResult; + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Zoom in/out image on scrolling + * + * @private + * @param {WheelEvent} e + */ + _onImgScroll(e) { + e.preventDefault(); + if (e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0) { + this._addZoomSteps(1); + } else { + this._addZoomSteps(-1); + } + }, + /** + * Allow user to close by clicking anywhere (mobile...). Destroying the modal + * without using 'hide' would leave a modal-open in the view. + * @private + * @param {Event} e + */ + _onZoomerClick(e) { + e.preventDefault(); + this.$el.modal('hide'); + }, + /** + * @private + * @param {Event} e + */ + _onZoomInClick(e) { + e.stopPropagation(); + this._addZoomSteps(1); + }, + /** + * @private + * @param {Event} e + */ + _onZoomOutClick(e) { + e.stopPropagation(); + this._addZoomSteps(-1); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Zoom in / out the image by changing the scale by the given number of steps. + * + * @private + * @param {integer} zoomStepNumber - Number of zoom steps applied to the scale of + * the image. It can be negative, in order to zoom out. Step is set to 0.1. + */ + _addZoomSteps(zoomStepNumber) { + const image = this.el.querySelector('.o_survey_img_zoom_image'); + const body = this.el.querySelector('.o_survey_img_zoom_body'); + const imageWidth = image.clientWidth; + const imageHeight = image.clientHeight; + const bodyWidth = body.clientWidth; + const bodyHeight = body.clientHeight; + const newZoomImageScale = this.zoomImageScale + zoomStepNumber * 0.1; + if (newZoomImageScale <= 0.2) { + // Prevent the user from de-zooming too much + return; + } + if (zoomStepNumber > 0 && (imageWidth * newZoomImageScale > bodyWidth || imageHeight * newZoomImageScale > bodyHeight)) { + // Prevent to user to further zoom in as the new image would becomes too large or too high for the screen. + // Dezooming is still allowed to bring back image into frame (use case: resizing screen). + return; + } + // !important is needed to prevent default 'no-transform' on smaller screens. + image.setAttribute('style', 'transform: scale(' + newZoomImageScale + ') !important'); + this.zoomImageScale = newZoomImageScale; + }, +}); diff --git a/addons/survey/static/src/scss/survey_templates_form.scss b/addons/survey/static/src/scss/survey_templates_form.scss index 42919a4b225..dadcb408992 100644 --- a/addons/survey/static/src/scss/survey_templates_form.scss +++ b/addons/survey/static/src/scss/survey_templates_form.scss @@ -253,6 +253,16 @@ _::-webkit-full-page-media, _:future, :root .o_survey_wrap { } } + .o_survey_choice_img img { + max-width: 95%; + max-height: 60vh; + cursor: zoom-in; + &:hover { + box-sizing: border-box; + box-shadow: 0 0 5px 2px grey; + } + } + .o_survey_choice_key { width: 25px; height: 25px; @@ -473,4 +483,57 @@ _::-webkit-full-page-media, _:future, :root .o_survey_wrap { .o_survey_choice_question_skipped { color: darken($warning, 10%); } + + .o_survey_choice_img img { + cursor: default; + &:hover { + box-shadow: none; + } + } } + +/********************************************************** + Zoomer Specific Style (SurveyImageZoomer widget) + When the width is small (mobile), let space above and below + to indicate that the user can close it by clicking out. + **********************************************************/ + +.o_survey_img_zoom_modal { + cursor: pointer; + .o_survey_img_zoom_dialog { + background-color: rgba(0,0,0,0.65); + @include media-breakpoint-down(sm) { + height: 80% !important; + } + .o_survey_img_zoom_body { + font-size: 1.5rem; + img { + max-width: 90%; + min-width: clamp(250px, 60%, 450px); + max-height: 90%; + object-fit: contain; + } + .o_survey_img_zoom_close_btn { + right: 12px; + top: 12px; + z-index: 1; + } + .o_survey_img_zoom_controls_wrapper { + bottom: 5%; + .o_survey_img_zoom_in_btn, .o_survey_img_zoom_out_btn { + background-color: rgba(0,0,0,0.65); + &:hover .fa { + color: grey; + } + } + } + } + } +} + +// Avoid shifting (due to scroll bar) when opening the image zoom widget +.modal-open { + .o_survey_background { + overflow: auto !important; + } + } diff --git a/addons/survey/static/src/xml/survey_image_zoomer_templates.xml b/addons/survey/static/src/xml/survey_image_zoomer_templates.xml new file mode 100644 index 00000000000..d01c6873934 --- /dev/null +++ b/addons/survey/static/src/xml/survey_image_zoomer_templates.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/survey/views/survey_question_views.xml b/addons/survey/views/survey_question_views.xml index 3b67805f217..9a78a1e2fee 100644 --- a/addons/survey/views/survey_question_views.xml +++ b/addons/survey/views/survey_question_views.xml @@ -145,7 +145,8 @@ attrs="{'column_invisible': ['|', ('parent.scoring_type', '=', 'no_scoring'), ('parent.question_type', '=', 'matrix')]}"/> - + diff --git a/addons/survey/views/survey_templates.xml b/addons/survey/views/survey_templates.xml index c235564d94f..be6de09bce4 100644 --- a/addons/survey/views/survey_templates.xml +++ b/addons/survey/views/survey_templates.xml @@ -402,11 +402,11 @@ + class="o_survey_choice_img d-flex my-3 justify-content-center">