summaryrefslogtreecommitdiff
path: root/addons/website/static/src/snippets/s_image_gallery/options.js
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2019-12-18 02:05:34 +0100
committerqsm-odoo <qsm@odoo.com>2019-12-18 02:14:51 +0100
commitbb25b70a749bcfd6f84cb62936612e311fe6fe85 (patch)
tree1e0369470e1ed33f0ba89d2351f9494146866a22 /addons/website/static/src/snippets/s_image_gallery/options.js
parent885a015b8af83ab2674034af42f201f97ec45f5b (diff)
parentb0c220536c0faf2e7f9279eedb776bf3b7f18363 (diff)
[REF] website: move s_image_gallery snippet to dedicated files
Diffstat (limited to 'addons/website/static/src/snippets/s_image_gallery/options.js')
-rw-r--r--addons/website/static/src/snippets/s_image_gallery/options.js456
1 files changed, 456 insertions, 0 deletions
diff --git a/addons/website/static/src/snippets/s_image_gallery/options.js b/addons/website/static/src/snippets/s_image_gallery/options.js
new file mode 100644
index 00000000000..819ac1aedc0
--- /dev/null
+++ b/addons/website/static/src/snippets/s_image_gallery/options.js
@@ -0,0 +1,456 @@
+odoo.define('website.s_image_gallery_options', function (require) {
+'use strict';
+
+var core = require('web.core');
+var weWidgets = require('wysiwyg.widgets');
+var options = require('web_editor.snippets.options');
+
+var _t = core._t;
+var qweb = core.qweb;
+
+options.registry.gallery = options.Class.extend({
+ xmlDependencies: ['/website/static/src/xml/website.gallery.xml'],
+
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+
+ // The snippet should not be editable
+ this.$target.addClass('o_fake_not_editable').attr('contentEditable', false);
+
+ // Make sure image previews are updated if images are changed
+ this.$target.on('save', 'img', function (ev) {
+ var $img = $(ev.currentTarget);
+ var index = self.$target.find('.carousel-item.active').index();
+ self.$('.carousel:first li[data-target]:eq(' + index + ')')
+ .css('background-image', 'url(' + $img.attr('src') + ')');
+ });
+
+ // When the snippet is empty, an edition button is the default content
+ // TODO find a nicer way to do that to have editor style
+ this.$target.on('click', '.o_add_images', function (e) {
+ e.stopImmediatePropagation();
+ self.addImages(false);
+ });
+
+ this.$target.on('dropped', 'img', function (ev) {
+ self.mode(null, self.getMode());
+ if (!ev.target.height) {
+ $(ev.target).one('load', function () {
+ setTimeout(function () {
+ self.trigger_up('cover_update');
+ });
+ });
+ }
+ });
+
+ if (this.$('.container:first > *:not(div)').length) {
+ self.mode(null, self.getMode());
+ }
+
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ onBuilt: function () {
+ var uuid = new Date().getTime();
+ this.$target.find('.carousel').attr('id', 'slideshow_' + uuid);
+ this.$target.find('[data-target]').attr('data-target', '#slideshow_' + uuid);
+ },
+ /**
+ * @override
+ */
+ cleanForSave: function () {
+ if (this.$target.hasClass('slideshow')) {
+ this.$target.removeAttr('style');
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Allows to select images to add as part of the snippet.
+ *
+ * @see this.selectClass for parameters
+ */
+ addImages: function (previewMode) {
+ var $container = this.$('.container:first');
+ var dialog = new weWidgets.MediaDialog(this, {multiImages: true, onlyImages: true, mediaWidth: 1920});
+ var lastImage = _.last(this._getImages());
+ var index = lastImage ? this._getIndex(lastImage) : -1;
+ return new Promise(resolve => {
+ dialog.on('save', this, function (attachments) {
+ for (var i = 0; i < attachments.length; i++) {
+ $('<img/>', {
+ class: 'img img-fluid',
+ src: attachments[i].image_src,
+ 'data-index': ++index,
+ }).appendTo($container);
+ }
+ this.mode('reset', this.getMode());
+ this.trigger_up('cover_update');
+ resolve();
+ });
+ dialog.open();
+ });
+ },
+ /**
+ * Allows to change the number of columns when displaying images with a
+ * grid-like layout.
+ *
+ * @see this.selectClass for parameters
+ */
+ columns: function (previewMode, widgetValue, params) {
+ const nbColumns = parseInt(widgetValue || '1');
+ this.$target.attr('data-columns', nbColumns);
+
+ this.mode(previewMode, this.getMode(), {}); // TODO improve
+ },
+ /**
+ * Get the image target's layout mode (slideshow, masonry, grid or nomode).
+ *
+ * @returns {String('slideshow'|'masonry'|'grid'|'nomode')}
+ */
+ getMode: function () {
+ var mode = 'slideshow';
+ if (this.$target.hasClass('o_masonry')) {
+ mode = 'masonry';
+ }
+ if (this.$target.hasClass('o_grid')) {
+ mode = 'grid';
+ }
+ if (this.$target.hasClass('o_nomode')) {
+ mode = 'nomode';
+ }
+ return mode;
+ },
+ /**
+ * Displays the images with the "grid" layout.
+ */
+ grid: function () {
+ var imgs = this._getImages();
+ var $row = $('<div/>', {class: 'row'});
+ var columns = this._getColumns();
+ var colClass = 'col-lg-' + (12 / columns);
+ var $container = this._replaceContent($row);
+
+ _.each(imgs, function (img, index) {
+ var $img = $(img);
+ var $col = $('<div/>', {class: colClass});
+ $col.append($img).appendTo($row);
+ if ((index + 1) % columns === 0) {
+ $row = $('<div/>', {class: 'row'});
+ $row.appendTo($container);
+ }
+ });
+ this.$target.css('height', '');
+ },
+ /**
+ * Allows to changes the interval of automatic slideshow (not active in
+ * edit mode).
+ */
+ interval: function (previewMode, widgetValue) {
+ this.$target.find('.carousel:first').attr('data-interval', widgetValue || '0');
+ },
+ /**
+ * Displays the images with the "masonry" layout.
+ */
+ masonry: function () {
+ var self = this;
+ var imgs = this._getImages();
+ var columns = this._getColumns();
+ var colClass = 'col-lg-' + (12 / columns);
+ var cols = [];
+
+ var $row = $('<div/>', {class: 'row'});
+ this._replaceContent($row);
+
+ // Create columns
+ for (var c = 0; c < columns; c++) {
+ var $col = $('<div/>', {class: 'col o_snippet_not_selectable ' + colClass});
+ $row.append($col);
+ cols.push($col[0]);
+ }
+
+ // Dispatch images in columns by always putting the next one in the
+ // smallest-height column
+ while (imgs.length) {
+ var min = Infinity;
+ var $lowest;
+ _.each(cols, function (col) {
+ var $col = $(col);
+ var height = $col.is(':empty') ? 0 : $col.find('img').last().offset().top + $col.find('img').last().height() - self.$target.offset().top;
+ if (height < min) {
+ min = height;
+ $lowest = $col;
+ }
+ });
+ $lowest.append(imgs.pop());
+ }
+ },
+ /**
+ * Allows to change the images layout. @see grid, masonry, nomode, slideshow
+ *
+ * @see this.selectClass for parameters
+ */
+ mode: function (previewMode, widgetValue, params) {
+ widgetValue = widgetValue || 'slideshow'; // FIXME should not be needed
+ this.$target.css('height', '');
+ this[widgetValue]();
+ this.$target
+ .removeClass('o_nomode o_masonry o_grid o_slideshow')
+ .addClass('o_' + widgetValue);
+ this.trigger_up('cover_update');
+ },
+ /**
+ * Displays the images with the standard layout: floating images.
+ */
+ nomode: function () {
+ var $row = $('<div/>', {class: 'row'});
+ var imgs = this._getImages();
+
+ this._replaceContent($row);
+
+ _.each(imgs, function (img) {
+ var wrapClass = 'col-lg-3';
+ if (img.width >= img.height * 2 || img.width > 600) {
+ wrapClass = 'col-lg-6';
+ }
+ var $wrap = $('<div/>', {class: wrapClass}).append(img);
+ $row.append($wrap);
+ });
+ },
+ /**
+ * Allows to remove all images. Restores the snippet to the way it was when
+ * it was added in the page.
+ *
+ * @see this.selectClass for parameters
+ */
+ removeAllImages: function (previewMode) {
+ var $addImg = $('<div>', {
+ class: 'alert alert-info css_editable_mode_display text-center',
+ });
+ var $text = $('<span>', {
+ class: 'o_add_images',
+ style: 'cursor: pointer;',
+ text: _t(" Add Images"),
+ });
+ var $icon = $('<i>', {
+ class: ' fa fa-plus-circle',
+ });
+ this._replaceContent($addImg.append($icon).append($text));
+ },
+ /**
+ * Displays the images with a "slideshow" layout.
+ */
+ slideshow: function () {
+ var imgStyle = this.$el.find('.active[data-styling]').data('styling') || '';
+ var urls = _.map(this._getImages(), function (img) {
+ return $(img).attr('src');
+ });
+ var currentInterval = this.$target.find('.carousel:first').attr('data-interval');
+ var params = {
+ srcs: urls,
+ index: 0,
+ title: "",
+ interval: currentInterval || this.$target.data('interval') || 0,
+ id: 'slideshow_' + new Date().getTime(),
+ userStyle: imgStyle,
+ },
+ $slideshow = $(qweb.render('website.gallery.slideshow', params));
+ this._replaceContent($slideshow);
+ _.each(this.$('img'), function (img, index) {
+ $(img).attr({contenteditable: true, 'data-index': index});
+ });
+ this.$target.css('height', Math.round(window.innerHeight * 0.7));
+
+ // Apply layout animation
+ this.$target.off('slide.bs.carousel').off('slid.bs.carousel');
+ this.$('li.fa').off('click');
+ },
+ /**
+ * Allows to change the style of the individual images.
+ *
+ * @see this.selectClass for parameters
+ */
+ styling: function (previewMode, value) {
+ var classes = _.map(this.$el.find('[data-styling]'), function (el) {
+ return $(el).data('styling');
+ }).join(' ');
+ this.$('img').removeClass(classes).addClass(value);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Handles image removals and image index updates.
+ *
+ * @override
+ */
+ notify: async function (name, data) {
+ await this._super(...arguments);
+ if (name === 'image_removed') {
+ data.$image.remove(); // Force the removal of the image before reset
+ this.mode('reset', this.getMode());
+ } else if (name === 'image_index_request') {
+ var imgs = this._getImages();
+ var position = _.indexOf(imgs, data.$image[0]);
+ imgs.splice(position, 1);
+ switch (data.position) {
+ case 'first':
+ imgs.unshift(data.$image[0]);
+ break;
+ case 'prev':
+ imgs.splice(position - 1, 0, data.$image[0]);
+ break;
+ case 'next':
+ imgs.splice(position + 1, 0, data.$image[0]);
+ break;
+ case 'last':
+ imgs.push(data.$image[0]);
+ break;
+ }
+ _.each(imgs, function (img, index) {
+ // Note: there might be more efficient ways to do that but it is
+ // more simple this way and allows compatibility with 10.0 where
+ // indexes were not the same as positions.
+ $(img).attr('data-index', index);
+ });
+ this.mode('reset', this.getMode());
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _computeWidgetState: function (methodName, params) {
+ switch (methodName) {
+ case 'mode': {
+ let activeModeName = 'slideshow';
+ for (const modeName of params.possibleValues) {
+ if (this.$target.hasClass(`o_${modeName}`)) {
+ activeModeName = modeName;
+ break;
+ }
+ }
+ this.activeMode = activeModeName;
+ return activeModeName;
+ }
+ case 'interval': {
+ const carousel = this.$target[0].querySelector('.carousel');
+ return (carousel && carousel.dataset.interval || '0');
+ }
+ case 'columns': {
+ return `${this._getColumns()}`;
+ }
+ case 'styling': {
+ const img = this.$target[0].querySelector('img');
+ if (!img) {
+ return '';
+ }
+ for (const className of params.possibleValues) {
+ if (className && img.classList.contains(className)) {
+ return className;
+ }
+ }
+ return '';
+ }
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * Returns the images, sorted by index.
+ *
+ * @private
+ * @returns {DOMElement[]}
+ */
+ _getImages: function () {
+ var imgs = this.$('img').get();
+ var self = this;
+ imgs.sort(function (a, b) {
+ return self._getIndex(a) - self._getIndex(b);
+ });
+ return imgs;
+ },
+ /**
+ * Returns the index associated to a given image.
+ *
+ * @private
+ * @param {DOMElement} img
+ * @returns {integer}
+ */
+ _getIndex: function (img) {
+ return img.dataset.index || 0;
+ },
+ /**
+ * Returns the currently selected column option.
+ *
+ * @private
+ * @returns {integer}
+ */
+ _getColumns: function () {
+ return parseInt(this.$target.attr('data-columns')) || 3;
+ },
+ /**
+ * Empties the container, adds the given content and returns the container.
+ *
+ * @private
+ * @param {jQuery} $content
+ * @returns {jQuery} the main container of the snippet
+ */
+ _replaceContent: function ($content) {
+ var $container = this.$('.container:first');
+ $container.empty().append($content);
+ return $container;
+ },
+});
+
+options.registry.gallery_img = options.Class.extend({
+ /**
+ * Rebuilds the whole gallery when one image is removed.
+ *
+ * @override
+ */
+ onRemove: function () {
+ this.trigger_up('option_update', {
+ optionName: 'gallery',
+ name: 'image_removed',
+ data: {
+ $image: this.$target,
+ },
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Allows to change the position of an image (its order in the image set).
+ *
+ * @see this.selectClass for parameters
+ */
+ position: function (previewMode, widgetValue, params) {
+ this.trigger_up('deactivate_snippet');
+ this.trigger_up('option_update', {
+ optionName: 'gallery',
+ name: 'image_index_request',
+ data: {
+ $image: this.$target,
+ position: widgetValue,
+ },
+ });
+ },
+});
+});