summaryrefslogtreecommitdiff
path: root/addons/website_slides/static/src/js/slides_upload.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website_slides/static/src/js/slides_upload.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website_slides/static/src/js/slides_upload.js')
-rw-r--r--addons/website_slides/static/src/js/slides_upload.js678
1 files changed, 678 insertions, 0 deletions
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
new file mode 100644
index 00000000..3ab24914
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -0,0 +1,678 @@
+odoo.define('website_slides.upload_modal', function (require) {
+'use strict';
+
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var publicWidget = require('web.public.widget');
+var utils = require('web.utils');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var SlideUploadDialog = Dialog.extend({
+ template: 'website.slide.upload.modal',
+ events: _.extend({}, Dialog.prototype.events, {
+ 'click .o_wslides_js_upload_install_button': '_onClickInstallModule',
+ 'click .o_wslides_select_type': '_onClickSlideTypeIcon',
+ 'change input#upload': '_onChangeSlideUpload',
+ 'change input#url': '_onChangeSlideUrl',
+ }),
+
+ /**
+ * @override
+ * @param {Object} parent
+ * @param {Object} options holding channelId and optionally upload and publish control parameters
+ * @param {Object} options.modulesToInstall: list of additional modules to
+ * install {id: module ID, name: module short description}
+ */
+ init: function (parent, options) {
+ options = _.defaults(options || {}, {
+ title: _t("Upload a document"),
+ size: 'medium',
+ });
+ this._super(parent, options);
+ this._setup();
+
+ this.channelID = parseInt(options.channelId, 10);
+ this.defaultCategoryID = parseInt(options.categoryId,10);
+ this.canUpload = options.canUpload === 'True';
+ this.canPublish = options.canPublish === 'True';
+ this.modulesToInstall = options.modulesToInstall ? JSON.parse(options.modulesToInstall.replace(/'/g, '"')) : null;
+ this.modulesToInstallStatus = null;
+
+ this.set('state', '_select');
+ this.on('change:state', this, this._onChangeType);
+ this.set('can_submit_form', false);
+ this.on('change:can_submit_form', this, this._onChangeCanSubmitForm);
+
+ this.file = {};
+ this.isValidUrl = true;
+ },
+ start: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ self._resetModalButton();
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {string} message
+ */
+ _alertDisplay: function (message) {
+ this._alertRemove();
+ $('<div/>', {
+ "class": 'alert alert-warning',
+ id: 'upload-alert',
+ role: 'alert'
+ }).text(message).insertBefore(this.$('form'));
+ },
+ _alertRemove: function () {
+ this.$('#upload-alert').remove();
+ },
+ /**
+ * Section and tags management from select2
+ *
+ * @private
+ */
+ _bindSelect2Dropdown: function () {
+ var self = this;
+ this.$('#category_id').select2(this._select2Wrapper(_t('Section'), false,
+ function () {
+ return self._rpc({
+ route: '/slides/category/search_read',
+ params: {
+ fields: ['name'],
+ domain: [['channel_id', '=', self.channelID]],
+ }
+ });
+ })
+ );
+ this.$('#tag_ids').select2(this._select2Wrapper(_t('Tags'), true, function () {
+ return self._rpc({
+ route: '/slides/tag/search_read',
+ params: {
+ fields: ['name'],
+ domain: [],
+ }
+ });
+ }));
+ },
+ _fetchUrlPreview: function (url) {
+ return this._rpc({
+ route: '/slides/prepare_preview/',
+ params: {
+ 'url': url,
+ 'channel_id': this.channelID
+ },
+ });
+ },
+ _formSetFieldValue: function (fieldId, value) {
+ this.$('form').find('#'+fieldId).val(value);
+ },
+ _formGetFieldValue: function (fieldId) {
+ return this.$('#'+fieldId).val();
+ },
+ _formValidate: function () {
+ var form = this.$("form");
+ form.addClass('was-validated');
+ return form[0].checkValidity() && this.isValidUrl;
+ },
+ /**
+ * Extract values to submit from form, force the slide_type according to
+ * filled values.
+ *
+ * @private
+ */
+ _formValidateGetValues: function (forcePublished) {
+ var canvas = this.$('#data_canvas')[0];
+ var values = _.extend({
+ 'channel_id': this.channelID,
+ 'name': this._formGetFieldValue('name'),
+ 'url': this._formGetFieldValue('url'),
+ 'description': this._formGetFieldValue('description'),
+ 'duration': this._formGetFieldValue('duration'),
+ 'is_published': forcePublished,
+ }, this._getSelect2DropdownValues()); // add tags and category
+
+ // default slide_type (for webpage for instance)
+ if (_.contains(this.slide_type_data), this.get('state')) {
+ values['slide_type'] = this.get('state');
+ }
+
+ if (this.file.type === 'application/pdf') {
+ _.extend(values, {
+ 'image_1920': canvas.toDataURL().split(',')[1],
+ 'slide_type': canvas.height > canvas.width ? 'document' : 'presentation',
+ 'mime_type': this.file.type,
+ 'datas': this.file.data
+ });
+ } else if (values['slide_type'] === 'webpage') {
+ _.extend(values, {
+ 'mime_type': 'text/html',
+ 'image_1920': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data,
+ });
+ } else if (/^image\/.*/.test(this.file.type)) {
+ if (values['slide_type'] === 'presentation') {
+ _.extend(values, {
+ 'slide_type': 'infographic',
+ 'mime_type': this.file.type === 'image/svg+xml' ? 'image/png' : this.file.type,
+ 'datas': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data
+ });
+ } else {
+ _.extend(values, {
+ 'image_1920': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data,
+ });
+ }
+ }
+ return values;
+ },
+ /**
+ * @private
+ */
+ _fileReset: function () {
+ var control = this.$('#upload');
+ control.replaceWith(control = control.clone(true));
+ this.file.name = false;
+ },
+
+ _getModalButtons: function () {
+ var btnList = [];
+ var state = this.get('state');
+ if (state === '_select') {
+ btnList.push({text: _t("Cancel"), classes: 'o_w_slide_cancel', close: true});
+ } else if (state === '_import') {
+ if (! this.modulesToInstallStatus.installing) {
+ btnList.push({text: this.modulesToInstallStatus.failed ? _t("Retry") : _t("Install"), classes: 'btn-primary', click: this._onClickInstallModuleConfirm.bind(this)});
+ }
+ btnList.push({text: _t("Discard"), classes: 'o_w_slide_go_back', click: this._onClickGoBack.bind(this)});
+ } else if (state !== '_upload') { // no button when uploading
+ if (this.canUpload) {
+ if (this.canPublish) {
+ btnList.push({text: _t("Save & Publish"), classes: 'btn-primary o_w_slide_upload o_w_slide_upload_published', click: this._onClickFormSubmit.bind(this)});
+ btnList.push({text: _t("Save"), classes: 'o_w_slide_upload', click: this._onClickFormSubmit.bind(this)});
+ } else {
+ btnList.push({text: _t("Save"), classes: 'btn-primary o_w_slide_upload', click: this._onClickFormSubmit.bind(this)});
+ }
+ }
+ btnList.push({text: _t("Discard"), classes: 'o_w_slide_go_back', click: this._onClickGoBack.bind(this)});
+ }
+ return btnList;
+ },
+ /**
+ * Get value for category_id and tag_ids (ORM cmd) to send to server
+ *
+ * @private
+ */
+ _getSelect2DropdownValues: function () {
+ var result = {};
+ var self = this;
+ // tags
+ var tagValues = [];
+ _.each(this.$('#tag_ids').select2('data'), function (val) {
+ if (val.create) {
+ tagValues.push([0, 0, {'name': val.text}]);
+ } else {
+ tagValues.push([4, val.id]);
+ }
+ });
+ if (tagValues) {
+ result['tag_ids'] = tagValues;
+ }
+ // category
+ if (!self.defaultCategoryID) {
+ var categoryValue = this.$('#category_id').select2('data');
+ if (categoryValue && categoryValue.create) {
+ result['category_id'] = [0, {'name': categoryValue.text}];
+ } else if (categoryValue) {
+ result['category_id'] = [categoryValue.id];
+ this.categoryID = categoryValue.id;
+ }
+ } else {
+ result['category_id'] = [self.defaultCategoryID];
+ this.categoryID = self.defaultCategoryID;
+ }
+ return result;
+ },
+ /**
+ * Reset the footer buttons, according to current state of modal
+ *
+ * @private
+ */
+ _resetModalButton: function () {
+ this.set_buttons(this._getModalButtons());
+ },
+ /**
+ * Wrapper for select2 load data from server at once and store it.
+ *
+ * @private
+ * @param {String} Placeholder for element.
+ * @param {bool} true for multiple selection box, false for single selection
+ * @param {Function} Function to fetch data from remote location should return a Promise
+ * resolved data should be array of object with id and name. eg. [{'id': id, 'name': 'text'}, ...]
+ * @param {String} [nameKey='name'] (optional) the name key of the returned record
+ * ('name' if not provided)
+ * @returns {Object} select2 wrapper object
+ */
+ _select2Wrapper: function (tag, multi, fetchFNC, nameKey) {
+ nameKey = nameKey || 'name';
+
+ var values = {
+ width: '100%',
+ placeholder: tag,
+ allowClear: true,
+ formatNoMatches: false,
+ selection_data: false,
+ fetch_rpc_fnc: fetchFNC,
+ formatSelection: function (data) {
+ if (data.tag) {
+ data.text = data.tag;
+ }
+ return data.text;
+ },
+ createSearchChoice: function (term, data) {
+ var addedTags = $(this.opts.element).select2('data');
+ if (_.filter(_.union(addedTags, data), function (tag) {
+ return tag.text.toLowerCase().localeCompare(term.toLowerCase()) === 0;
+ }).length === 0) {
+ if (this.opts.can_create) {
+ return {
+ id: _.uniqueId('tag_'),
+ create: true,
+ tag: term,
+ text: _.str.sprintf(_t("Create new %s '%s'"), tag, term),
+ };
+ } else {
+ return undefined;
+ }
+ }
+ },
+ fill_data: function (query, data) {
+ var self = this,
+ tags = {results: []};
+ _.each(data, function (obj) {
+ if (self.matcher(query.term, obj[nameKey])) {
+ tags.results.push({id: obj.id, text: obj[nameKey]});
+ }
+ });
+ query.callback(tags);
+ },
+ query: function (query) {
+ var self = this;
+ // fetch data only once and store it
+ if (!this.selection_data) {
+ this.fetch_rpc_fnc().then(function (data) {
+ self.can_create = data.can_create;
+ self.fill_data(query, data.read_results);
+ self.selection_data = data.read_results;
+ });
+ } else {
+ this.fill_data(query, this.selection_data);
+ }
+ }
+ };
+
+ if (multi) {
+ values['multiple'] = true;
+ }
+
+ return values;
+ },
+ /**
+ * Init the data relative to the support slide type to upload
+ *
+ * @private
+ */
+ _setup: function () {
+ this.slide_type_data = {
+ presentation: {
+ icon: 'fa-file-pdf-o',
+ label: _t('Presentation'),
+ template: 'website.slide.upload.modal.presentation',
+ },
+ webpage: {
+ icon: 'fa-file-text',
+ label: _t('Web Page'),
+ template: 'website.slide.upload.modal.webpage',
+ },
+ video: {
+ icon: 'fa-video-camera',
+ label: _t('Video'),
+ template: 'website.slide.upload.modal.video',
+ },
+ quiz: {
+ icon: 'fa-question-circle',
+ label: _t('Quiz'),
+ template: 'website.slide.upload.quiz'
+ }
+ };
+ },
+ /**
+ * Show the preview
+ * @private
+ */
+ _showPreviewColumn: function () {
+ this.$('.o_slide_tutorial').addClass('d-none');
+ this.$('.o_slide_preview').removeClass('d-none');
+ },
+ /**
+ * Hide the preview
+ * @private
+ */
+ _hidePreviewColumn: function () {
+ this.$('.o_slide_tutorial').removeClass('d-none');
+ this.$('.o_slide_preview').addClass('d-none');
+ },
+ /**
+ * @private
+ */
+ // TODO: Remove this part, as now SVG support in image resize tools is included
+ //Python PIL does not support SVG, so converting SVG to PNG
+ _svgToPng: function () {
+ var img = this.$el.find('img#slide-image')[0];
+ var canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ canvas.getContext('2d').drawImage(img, 0, 0);
+ return canvas.toDataURL('image/png').split(',')[1];
+ },
+ //--------------------------------------------------------------------------
+ // Handler
+ //--------------------------------------------------------------------------
+
+ _onChangeType: function () {
+ var currentType = this.get('state');
+ var tmpl;
+ this.$modal.find('.modal-dialog').removeClass('modal-lg');
+ if (currentType === '_select') {
+ tmpl = 'website.slide.upload.modal.select';
+ } else if (currentType === '_upload') {
+ tmpl = 'website.slide.upload.modal.uploading';
+ } else if (currentType === '_import') {
+ tmpl = 'website.slide.upload.modal.import';
+ } else {
+ tmpl = this.slide_type_data[currentType]['template'];
+ this.$modal.find('.modal-dialog').addClass('modal-lg');
+ }
+ this.$('.o_w_slide_upload_modal_container').empty();
+ this.$('.o_w_slide_upload_modal_container').append(QWeb.render(tmpl, {widget: this}));
+
+ this._resetModalButton();
+
+ if (currentType === '_import') {
+ this.set_title(_t("New Certification"));
+ } else {
+ this.set_title(_t("Upload a document"));
+ }
+ },
+ _onChangeCanSubmitForm: function (ev) {
+ if (this.get('can_submit_form')) {
+ this.$('.o_w_slide_upload').button('reset');
+ } else {
+ this.$('.o_w_slide_upload').button('loading');
+ }
+ },
+ _onChangeSlideUpload: function (ev) {
+ var self = this;
+ this._alertRemove();
+
+ var $input = $(ev.currentTarget);
+ var preventOnchange = $input.data('preventOnchange');
+ var $preview = self.$('#slide-image');
+
+ var file = ev.target.files[0];
+ if (!file) {
+ this.$('#slide-image').attr('src', '/website_slides/static/src/img/document.png');
+ this._hidePreviewColumn();
+ return;
+ }
+ var isImage = /^image\/.*/.test(file.type);
+ var loaded = false;
+ this.file.name = file.name;
+ this.file.type = file.type;
+ if (!(isImage || this.file.type === 'application/pdf')) {
+ this._alertDisplay(_t("Invalid file type. Please select pdf or image file"));
+ this._fileReset();
+ this._hidePreviewColumn();
+ return;
+ }
+ if (file.size / 1024 / 1024 > 25) {
+ this._alertDisplay(_t("File is too big. File size cannot exceed 25MB"));
+ this._fileReset();
+ this._hidePreviewColumn();
+ return;
+ }
+
+ utils.getDataURLFromFile(file).then(function (buffer) {
+ if (isImage) {
+ $preview.attr('src', buffer);
+ }
+ buffer = buffer.split(',')[1];
+ self.file.data = buffer;
+ self._showPreviewColumn();
+ });
+
+ if (file.type === 'application/pdf') {
+ var ArrayReader = new FileReader();
+ this.set('can_submit_form', false);
+ // file read as ArrayBuffer for pdfjsLib get_Document API
+ ArrayReader.readAsArrayBuffer(file);
+ ArrayReader.onload = function (evt) {
+ var buffer = evt.target.result;
+ var passwordNeeded = function () {
+ self._alertDisplay(_t("You can not upload password protected file."));
+ self._fileReset();
+ self.set('can_submit_form', true);
+ };
+ /**
+ * The following line fixes pdfjsLib 'Util' global variable.
+ * This is (most likely) related to #32181 which lazy loads most assets.
+ *
+ * That caused an issue where the global 'Util' variable from pdfjsLib can be
+ * (depending of which libraries load first) overridden by the global 'Util'
+ * variable of bootstrap.
+ * (See 'lib/bootstrap/js/util.js' and 'web/static/lib/pdfjs/build/pdfjs.js')
+ *
+ * This commit ensures that the global 'Util' variable is set to the one of pdfjsLib
+ * right before it's used.
+ *
+ * Eventually, we should update or get rid of one of the two libraries since they're
+ * not compatible together, or make a wrapper that makes them compatible.
+ * In the mean time, this small fix allows not refactoring all of this and can not
+ * cause much harm.
+ */
+ Util = window.pdfjsLib.Util;
+ window.pdfjsLib.getDocument(new Uint8Array(buffer), null, passwordNeeded).then(function getPdf(pdf) {
+ self._formSetFieldValue('duration', (pdf._pdfInfo.numPages || 0) * 5);
+ pdf.getPage(1).then(function getFirstPage(page) {
+ var scale = 1;
+ var viewport = page.getViewport(scale);
+ var canvas = document.getElementById('data_canvas');
+ var context = canvas.getContext('2d');
+ canvas.height = viewport.height;
+ canvas.width = viewport.width;
+ // Render PDF page into canvas context
+ page.render({
+ canvasContext: context,
+ viewport: viewport
+ }).then(function () {
+ var imageData = self.$('#data_canvas')[0].toDataURL();
+ $preview.attr('src', imageData);
+ if (loaded) {
+ self.set('can_submit_form', true);
+ }
+ loaded = true;
+ self._showPreviewColumn();
+ });
+ });
+ });
+ };
+ }
+
+ if (!preventOnchange) {
+ var input = file.name;
+ var inputVal = input.substr(0, input.lastIndexOf('.')) || input;
+ if (this._formGetFieldValue('name') === "") {
+ this._formSetFieldValue('name', inputVal);
+ }
+ }
+ },
+ _onChangeSlideUrl: function (ev) {
+ var self = this;
+ var url = $(ev.target).val();
+ this._alertRemove();
+ this.isValidUrl = false;
+ this.set('can_submit_form', false);
+ this._fetchUrlPreview(url).then(function (data) {
+ self.set('can_submit_form', true);
+ if (data.error) {
+ self._alertDisplay(data.error);
+ } else {
+ if (data.completion_time) {
+ // hours to minutes conversion
+ self._formSetFieldValue('duration', Math.round(data.completion_time * 60));
+ }
+ self.$('#slide-image').attr('src', data.url_src);
+ self._formSetFieldValue('name', data.title);
+ self._formSetFieldValue('description', data.description);
+
+ self.isValidUrl = true;
+ self._showPreviewColumn();
+ }
+ });
+ },
+
+ _onClickInstallModule: function (ev) {
+ var $btn = $(ev.currentTarget);
+ var moduleId = $btn.data('moduleId');
+ if (this.modulesToInstallStatus) {
+ this.set('state', '_import');
+ if (this.modulesToInstallStatus.installing) {
+ this.$('#o_wslides_install_module_text')
+ .text(_.str.sprintf(_t('Already installing "%s".'), this.modulesToInstallStatus.name));
+ } else if (this.modulesToInstallStatus.failed) {
+ this.$('#o_wslides_install_module_text')
+ .text(_.str.sprintf(_t('Failed to install "%s".'), this.modulesToInstallStatus.name));
+ }
+ } else {
+ this.modulesToInstallStatus = _.extend({}, _.find(this.modulesToInstall, function (item) { return item.id === moduleId; }));
+ this.set('state', '_import');
+ this.$('#o_wslides_install_module_text')
+ .text(_.str.sprintf(_t('Do you want to install the "%s" app?'), this.modulesToInstallStatus.name));
+ }
+ },
+
+ _onClickInstallModuleConfirm: function () {
+ var self = this;
+ var $el = this.$('#o_wslides_install_module_text');
+ $el.text(_.str.sprintf(_t('Installing "%s".'), this.modulesToInstallStatus.name));
+ this.modulesToInstallStatus.installing = true;
+ this._resetModalButton();
+ this._rpc({
+ model: 'ir.module.module',
+ method: 'button_immediate_install',
+ args: [[this.modulesToInstallStatus.id]],
+ }).then(function () {
+ window.location.href = window.location.origin + window.location.pathname + '?enable_slide_upload';
+ }, function () {
+ $el.text(_.str.sprintf(_t('Failed to install "%s".'), self.modulesToInstallStatus.name));
+ self.modulesToInstallStatus.installing = false;
+ self.modulesToInstallStatus.failed = true;
+ self._resetModalButton();
+ });
+ },
+
+ _onClickGoBack: function () {
+ this.set('state', '_select');
+ this.isValidUrl = true;
+ if (this.modulesToInstallStatus && !this.modulesToInstallStatus.installing) {
+ this.modulesToInstallStatus = null;
+ }
+ },
+
+ _onClickFormSubmit: function (ev) {
+ var self = this;
+ var $btn = $(ev.currentTarget);
+ if (this._formValidate()) {
+ var values = this._formValidateGetValues($btn.hasClass('o_w_slide_upload_published')); // get info before changing state
+ var oldType = this.get('state');
+ this.set('state', '_upload');
+ return this._rpc({
+ route: '/slides/add_slide',
+ params: values,
+ }).then(function (data) {
+ self._onFormSubmitDone(data, oldType);
+ });
+ }
+ },
+
+ _onFormSubmitDone: function (data, oldType) {
+ if (data.error) {
+ this.set('state', oldType);
+ this._alertDisplay(data.error);
+ } else {
+ window.location = data.url;
+ }
+ },
+
+ _onClickSlideTypeIcon: function (ev) {
+ var $elem = this.$(ev.currentTarget);
+ var slideType = $elem.data('slideType');
+ this.set('state', slideType);
+
+ this._bindSelect2Dropdown(); // rebind select2 at each modal body rendering
+ },
+});
+
+publicWidget.registry.websiteSlidesUpload = publicWidget.Widget.extend({
+ selector: '.o_wslides_js_slide_upload',
+ xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'],
+ events: {
+ 'click': '_onUploadClick',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ // Automatically open the upload dialog if requested from query string
+ if (this.$el.attr('data-open-modal')) {
+ this.$el.removeAttr('data-open-modal');
+ this._openDialog(this.$el);
+ }
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ _openDialog: function ($element) {
+ var data = $element.data();
+ return new SlideUploadDialog(this, data).open();
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onUploadClick: function (ev) {
+ ev.preventDefault();
+ this._openDialog($(ev.currentTarget));
+ },
+});
+
+return {
+ SlideUploadDialog: SlideUploadDialog,
+ websiteSlidesUpload: publicWidget.registry.websiteSlidesUpload
+};
+
+});