diff --git a/cmis_field/controllers/main.py b/cmis_field/controllers/main.py index 14495c44..483c2703 100644 --- a/cmis_field/controllers/main.py +++ b/cmis_field/controllers/main.py @@ -27,6 +27,18 @@ def create_document_field_value(self, model_name, res_id, field_name, documents) return {'value': value} return {'value': None} + @http.route( + '/web/cmis/field/cmis_document/get_document_parent', + type='json', + methods=['POST'], + auth="user" + ) + def get_document_parent(self, model_name, res_id, backend_id, field_name): + env = http.request.env + record = env[model_name].browse(int(res_id)) + backend = env["cmis.backend"].browse(int(backend_id)) + return record._fields[field_name].get_create_parents(record, backend)[record.id] + def _decode_files(self, documents): for doc in documents: file_ = doc.get("data") diff --git a/cmis_field/fields/cmis_document.py b/cmis_field/fields/cmis_document.py index 1555d10c..bc06009a 100644 --- a/cmis_field/fields/cmis_document.py +++ b/cmis_field/fields/cmis_document.py @@ -61,7 +61,8 @@ def _description_backend(self, env): else: msg = _('No backend found. Please check your configuration.') return {'backend_error': msg} - return backend.get_web_description()[backend.id] + result = backend.get_web_description()[backend.id] + return result def get_cmis_object(self, record): """Returns an instance of @@ -135,10 +136,9 @@ def clean_up_document(cmis_object_id, backend_id, dbname): pass # remove created resource in case of rollback - test_mode = getattr(threading.currentThread(), 'testing', False) + test_mode = getattr(threading.current_thread(), 'testing', False) if not test_mode: - record.env.cr.after( - 'rollback', + record.env.cr.postrollback.add( partial( clean_up_document, object_id, diff --git a/cmis_web/__manifest__.py b/cmis_web/__manifest__.py index f1f8e161..9faaae1c 100644 --- a/cmis_web/__manifest__.py +++ b/cmis_web/__manifest__.py @@ -18,6 +18,7 @@ "web.assets_backend": [ "/cmis_web/static/lib/cmisjs/superagent.7.2.0.js", "/cmis_web/static/lib/cmisjs/cmis.0.3.1.js", + "/cmis_web/static/src/cmis_document/*", "/cmis_web/static/src/cmis_folder/cmis_folder.js", "/cmis_web/static/src/cmis_folder/cmis_folder.scss", "/cmis_web/static/src/cmis_folder/cmis_folder.xml", diff --git a/cmis_web/static/src/cmis_actions/cmis_actions.js b/cmis_web/static/src/cmis_actions/cmis_actions.js index 70cde9b2..f4fe926d 100644 --- a/cmis_web/static/src/cmis_actions/cmis_actions.js +++ b/cmis_web/static/src/cmis_actions/cmis_actions.js @@ -11,16 +11,24 @@ import {CmisAttachmentViewer} from "../cmis_attachment_viewer/cmis_attachment_vi import {CmisObjectWrapper} from "../cmis_object_wrapper_service"; import {Dropdown} from "@web/core/dropdown/dropdown"; import {DropdownItem} from "@web/core/dropdown/dropdown_item"; -import {cmisTableProps} from "../cmis_table/cmis_table"; import {registry} from "@web/core/registry"; import {useService} from "@web/core/utils/hooks"; -const {Component} = owl; +const {Component, useState} = owl; export class CmisActions extends Component { setup() { this.dialogService = useService("dialog"); this.allowableActions = this.props.cmisObject.allowableActions; + this.dynamicActions = this.props.dynamicActions || {}; + + this.state = useState({ + dynamicActions: this.dynamicActions, + }); + } + + onClickDynamicAction(name) { + this.props.dynamicActions[name].actionClick(this.props.cmisObject); } onClickDownload() { @@ -28,9 +36,10 @@ export class CmisActions extends Component { } onClickPreview() { + const props = this.props; this.dialogService.add(CmisAttachmentViewer, { - cmisObject: this.props.cmisObject, - cmisFolderObjects: this.props.cmisFolderObjects, + cmisObject: props.cmisObject, + cmisFolderObjects: [props.cmisObject], }); } @@ -50,9 +59,19 @@ export class CmisActions extends Component { CmisActions.template = "cmis_web.CmisActions"; CmisActions.components = {Dropdown, DropdownItem}; CmisActions.props = { - ...cmisTableProps, cmisObject: CmisObjectWrapper, - cmisFolderObjects: {type: Array, element: CmisObjectWrapper}, + renameObject: Function, + updateDocumentContent: Function, + deleteObject: { + type: Function, + optional: true, + }, + dynamicActions: [ + { + type: Object, + optional: true, + }, + ], }; registry.category("view_widgets").add("cmis_actions", CmisActions); diff --git a/cmis_web/static/src/cmis_actions/cmis_actions.xml b/cmis_web/static/src/cmis_actions/cmis_actions.xml index aef4868f..bb9585d0 100644 --- a/cmis_web/static/src/cmis_actions/cmis_actions.xml +++ b/cmis_web/static/src/cmis_actions/cmis_actions.xml @@ -28,6 +28,24 @@ > + + + +
+ +
+
+
Rename
diff --git a/cmis_web/static/src/cmis_document/cmis_document.js b/cmis_web/static/src/cmis_document/cmis_document.js new file mode 100644 index 00000000..d3985bad --- /dev/null +++ b/cmis_web/static/src/cmis_document/cmis_document.js @@ -0,0 +1,297 @@ +/** @odoo-module **/ +/* global cmis */ + +/* --------------------------------------------------------- ++ * Odoo cmis_web ++ * Authors Laurent Mignon 2016, Quentin Groulard 2023 Acsone SA/NV ++ * License in __openerp__.py at root level of the module ++ *--------------------------------------------------------- ++*/ + +import {AddDocumentDialog} from "../add_document_dialog/add_document_dialog"; +import {CmisActions} from "../cmis_actions/cmis_actions"; +import {CmisObjectWrapper} from "../cmis_object_wrapper_service"; +import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog"; +import {RenameDialog} from "../rename_dialog/rename_dialog"; +import {UpdateDocumentContentDialog} from "../update_document_content_dialog/update_document_content_dialog"; +import {WarningDialog} from "@web/core/errors/error_dialogs"; + +import {registry} from "@web/core/registry"; +import {sprintf} from "@web/core/utils/strings"; +import {standardFieldProps} from "@web/views/fields/standard_field_props"; +import {useService} from "@web/core/utils/hooks"; + +import framework from "web.framework"; + +const {Component, onWillRender, useState} = owl; + +export class CmisDocumentField extends Component { + setup() { + this.rpc = useService("rpc"); + this.orm = useService("orm"); + this.cmisObjectWrapperService = useService("cmisObjectWrapperService"); + this.dialogService = useService("dialog"); + + this.backend = this.props.backend; + this.state = useState({ + value: this.props.value, + cmisObjectsWrap: [], + cmisObjectWrap: {}, + allowableActions: {}, + hasData: false, + }); + + this.cmisSession = null; + this.documentId = null; + this.displayDocumentId = null; + + this.initCmisSession(); + + onWillRender(async () => { + this.setDocumentId(); + }); + } + + getCmisObjectWrapperParams() { + return {}; + } + + initCmisSession() { + if (this.backend.backend_error) { + this.dialogService.add(WarningDialog, { + title: "CMIS Error", + message: this.backend.backend_error, + }); + return; + } + this.cmisSession = cmis.createSession(this.backend.location); + this.cmisSession.setGlobalHandlers( + this.onCmisError.bind(this), + this.onCmisError.bind(this) + ); + this.cmisSession.setCharacterSet(document.characterSet); + } + + get dynamicActionsProps() { + const props = { + renameObject: this.renameObject.bind(this), + updateDocumentContent: this.updateDocumentContent.bind(this), + dynamicActions: { + delete_link: { + name: this.env._t("Delete Link"), + actionClick: this.deleteLink.bind(this), + }, + }, + }; + return props; + } + + async setDocumentId() { + if (this.documentId === this.state.value) { + return; + } + this.documentId = this.state.value; + + if (!this.documentId) { + return; + } + + var self = this; + const loadCmisRepositories = new Promise(function (resolve, reject) { + if (self.cmisSession.repositories) { + resolve(); + } + self.cmisSession + .loadRepositories() + .ok(() => resolve()) + .notOk((error) => reject(error)); + }); + + loadCmisRepositories.then(() => this.displayDocument()); + } + + async displayDocument() { + if (this.displayDocumentId === this.documentId) { + return; + } + this.displayDocumentId = this.documentId; + this.queryCmisData(); + } + + async queryCmisData() { + var self = this; + const options = { + includeAllowableActions: true, + }; + const cmisData = await new Promise((resolve) => { + self.cmisSession + .getObject(self.displayDocumentId, "latest", options) + .ok(function (data) { + resolve(data); + }); + }); + const params = this.getCmisObjectWrapperParams(); + this.state.allowableActions = cmisData.allowableActions; + this.state.allowableActions.canDeleteObject = false; + this.state.cmisObjectWrap = new CmisObjectWrapper( + cmisData, + this.cmisSession, + params + ); + this.state.hasData = true; + } + + onClickAddDocument() { + const dialogProps = { + confirm: (files) => { + this._getDocumentsFromFiles(files).then((documents) => { + this.uploadFile(documents); + }); + }, + }; + this.dialogService.add(AddDocumentDialog, dialogProps); + } + + onCmisError(error) { + framework.unblockUI(); + if (error) { + this.dialogService.add(WarningDialog, { + title: "CMIS Error", + message: error.body.message, + }); + } + } + + async uploadFile(documents) { + if (!this.props.record.resId) { + this.dialogService.add(WarningDialog, { + title: "CMIS Error", + message: this.env._t("Create your object first"), + }); + return; + } + const cmisValue = await this.rpc("/web/cmis/field/create_document_value", { + model_name: this.props.record.resModel, + res_id: this.props.record.data.id, + field_name: this.props.name, + documents: documents, + }); + await this.props.record.load(); + this.props.record.model.notify(); + this.state.value = cmisValue.value; + } + + _getDocumentsFromFiles(files) { + return Promise.all( + _.map(files, function (file) { + return new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function () { + resolve({ + name: file.name, + mimetype: file.type, + data: reader.result, + }); + }; + reader.onerror = (error) => reject(error); + }); + }) + ); + } + + renameObject(cmisObject) { + var self = this; + const dialogProps = { + title: `Rename ${cmisObject.name}`, + name: cmisObject.name, + confirm: (newName) => { + if (newName !== cmisObject.name) { + this.cmisSession + .updateProperties(cmisObject.objectId, {"cmis:name": newName}) + .ok(function () { + self.queryCmisData(); + }); + } + }, + }; + this.dialogService.add(RenameDialog, dialogProps); + } + + updateDocumentContent(cmisObject) { + var self = this; + const dialogProps = { + title: `Update content of ${cmisObject.name}`, + confirm: (file) => { + if (file) { + this.cmisSession + .setContentStream(cmisObject.objectId, file, true, file.name) + .ok(function () { + self.queryCmisData(); + }); + } + }, + }; + this.dialogService.add(UpdateDocumentContentDialog, dialogProps); + } + + deleteLink(cmisObject) { + const self = this; + const dialogProps = { + title: "Delete Link with the file", + body: sprintf('Confirm the link deletion of "%s".', cmisObject.name), + confirmLabel: "Delete link", + confirm: () => { + return self.performDeleteLink(); + }, + cancel: () => { + return; + }, + }; + this.dialogService.add(ConfirmationDialog, dialogProps); + } + + async performDeleteLink() { + const props = this.props; + const record = props.record; + const values = {}; + values[props.name] = false; + await this.orm.write(record.resModel, [record.data.id], values); + await record.load(); + record.model.notify(); + this.state.value = false; + } +} + +CmisDocumentField.template = "cmis_web.CmisDocumentField"; +CmisDocumentField.supportedTypes = ["cmis_document"]; +CmisDocumentField.components = {CmisActions}; +CmisDocumentField.props = { + ...standardFieldProps, + backend: [ + { + type: Object, + optional: true, + shape: { + id: Number, + location: String, + name: {type: String, optional: true}, + share_location: {type: String, optional: true}, + alfresco_api_location: {type: String, optional: true}, + }, + }, + { + type: Object, + optional: true, + shape: {backend_error: String}, + }, + ], +}; + +CmisDocumentField.extractProps = ({field}) => { + return { + backend: field.backend, + }; +}; + +registry.category("fields").add("cmis_document", CmisDocumentField); diff --git a/cmis_web/static/src/cmis_document/cmis_document.scss b/cmis_web/static/src/cmis_document/cmis_document.scss new file mode 100644 index 00000000..3a25aeea --- /dev/null +++ b/cmis_web/static/src/cmis_document/cmis_document.scss @@ -0,0 +1,17 @@ +.cmis-document { + .cmis-document-action { + cursor: pointer; + width: fit-content; + &:hover { + color: $btn-link-color !important; + } + + .action-label { + color: rgba(52, 58, 64, 0.4); + &:hover { + color: $btn-link-color !important; + } + } + + } +} diff --git a/cmis_web/static/src/cmis_document/cmis_document.xml b/cmis_web/static/src/cmis_document/cmis_document.xml new file mode 100644 index 00000000..9dd3de68 --- /dev/null +++ b/cmis_web/static/src/cmis_document/cmis_document.xml @@ -0,0 +1,41 @@ + + + +
+ + +
+
+
+ + Add a document +
+
+
+
+ + + + +
+ +
+
+
+
+
diff --git a/cmis_web/static/src/cmis_folder/cmis_folder.js b/cmis_web/static/src/cmis_folder/cmis_folder.js index e4ee0b78..dbe08223 100644 --- a/cmis_web/static/src/cmis_folder/cmis_folder.js +++ b/cmis_web/static/src/cmis_folder/cmis_folder.js @@ -74,6 +74,8 @@ export class CmisFolderField extends Component { }); }); const params = this.getCmisObjectWrapperParams(); + console.log("cmisObjectData"); + console.log(cmisObjectsData); this.state.cmisObjectsWrap = this.cmisObjectWrapperService.wrap( cmisObjectsData.objects, this.cmisSession, diff --git a/cmis_web/static/src/cmis_table/cmis_table.js b/cmis_web/static/src/cmis_table/cmis_table.js index 3dd87da8..a9598c6e 100644 --- a/cmis_web/static/src/cmis_table/cmis_table.js +++ b/cmis_web/static/src/cmis_table/cmis_table.js @@ -194,6 +194,17 @@ export class CmisTable extends Component { return value ? value : ""; } + get dynamicActionsProps() { + console.log(this.props); + const props = { + deleteObject: this.props.deleteObject, + renameObject: this.props.renameObject, + updateDocumentContent: this.props.updateDocumentContent, + dynamicActions: {}, + }; + return props; + } + get nbCols() { let nbCols = this.state.columns.length; // Column selector diff --git a/cmis_web/static/src/cmis_table/cmis_table.xml b/cmis_web/static/src/cmis_table/cmis_table.xml index 0cf1025d..c7b9e4aa 100644 --- a/cmis_web/static/src/cmis_table/cmis_table.xml +++ b/cmis_web/static/src/cmis_table/cmis_table.xml @@ -130,9 +130,8 @@ t-att-class="getCellClass(column, cmisObject)" >
diff --git a/cmis_web/static/src/js/document_viewer.js b/cmis_web/static/src/js/document_viewer.js deleted file mode 100644 index 6036618e..00000000 --- a/cmis_web/static/src/js/document_viewer.js +++ /dev/null @@ -1,397 +0,0 @@ -/** - * Most of the code copied from https://github.com/odoo/odoo/blob/c1fa3b8ab3dfa1306dbdd3b6dc910405a3357d16/addons/mail/static/src/js/document_viewer.js - */ -odoo.define('cmis_web.DocumentViewer', function (require) { -"use strict"; - -var core = require('web.core'); -var Widget = require('web.Widget'); - -var QWeb = core.qweb; - -var SCROLL_ZOOM_STEP = 0.1; -var ZOOM_STEP = 0.5; - -var DocumentViewer = Widget.extend({ - template: "cmis_web.DocumentViewer", - events: { - 'click .cmis_web_download_btn': '_onDownload', - 'click .cmis_web_viewer_img': '_onImageClicked', - 'click .cmis_web_viewer_video': '_onVideoClicked', - 'click .cmis_web_move_next': '_onNext', - 'click .cmis_web_move_previous': '_onPrevious', - 'click .cmis_web_rotate': '_onRotate', - 'click .cmis_web_zoom_in': '_onZoomIn', - 'click .cmis_web_zoom_out': '_onZoomOut', - 'click .cmis_web_zoom_reset': '_onZoomReset', - 'click .cmis_web_close_btn, .cmis_web_viewer_img_wrapper': '_onClose', - 'click .cmis_web_print_btn': '_onPrint', - 'DOMMouseScroll .cmis_web_viewer_content': '_onScroll', // Firefox - 'mousewheel .cmis_web_viewer_content': '_onScroll', // Chrome, Safari, IE - 'keydown': '_onKeydown', - 'mousedown .cmis_web_viewer_img': '_onStartDrag', - 'mousemove .cmis_web_viewer_content': '_onDrag', - 'mouseup .cmis_web_viewer_content': '_onEndDrag' - }, - /** - * The documentViewer takes an array of objects describing attachments in - * argument, and the ID of an active attachment (the one to display first). - * Documents that are not of type image or video are filtered out.² - * - * @override - * @param {Array[Object]} attachments list of attachments - * @param {integer} activeAttachmentID - */ - init: function (parent, cmisDocumentWrapped, cmisFolderContent) { - this._super.apply(this, arguments); - var self = this; - - if (!cmisFolderContent) { - cmisFolderContent = [cmisDocumentWrapped] - } - - this.documents = _.filter(cmisFolderContent, function (content) { - if (content.mimetype === undefined){ - return false; - } - var type = content.get_preview_type(); - - if (type) { - content.type = type; - if (content.type === 'image' || content.type ==='video'){ - content.viewer_url = content.get_content_url(); - } else { - content.viewer_url = self.get_pdf_preview_url(content); - } - return true; - } - }); - this.activeDocument = cmisDocumentWrapped; - this._reset(); - }, - /** - * Open a modal displaying the active attachment - * @override - */ - start: function () { - this.$el.modal('show'); - this.$el.on('hidden.bs.modal', _.bind(this._onDestroy, this)); - this.$('.cmis_web_viewer_img').on("load", _.bind(this._onImageLoaded, this)); - return this._super.apply(this, arguments); - }, - - /** - * Return a dictionary of http headers to use to query the preview url - */ - get_pdf_preview_url_headers: function(cmisObjectWrapped){ - if ($.ajaxSettings.headers){ - return JSON.parse(JSON.stringify($.ajaxSettings.headers)); - } - return {}; - }, - - /** - * Return a dictionary of parameters to use to query the preview url - */ - get_pdf_preview_url_params: function(cmisObjectWrapped){ - var title = cmisObjectWrapped.name; - var preview_url = cmisObjectWrapped.get_preview_url(); - var headers = this.get_pdf_preview_url_headers(cmisObjectWrapped); - return { - file: preview_url, - httpHeaders: JSON.stringify(headers), - title: title, - }; - }, - - /** - * Return the url used to launch the embeded document previewer - */ - get_pdf_preview_url: function(cmisObjectWrapped) { - var params = this.get_pdf_preview_url_params(cmisObjectWrapped); - // Create the previewer URL - var path = "/cmis_web/static/lib/pdfjs-1.9.426/web/odoo-viewer.html"; - return path + '?' + $.param(params); - }, - - // -------------------------------------------------------------------------- - // Private - // --------------------------------------------------------------------------- - - /** - * @private - */ - _next: function () { - var index = _.findIndex(this.documents, this.activeDocument); - index = (index + 1) % this.documents.length; - this.activeDocument = this.documents[index]; - this._updateContent(); - }, - /** - * @private - */ - _previous: function () { - var index = _.findIndex(this.documents, this.activeDocument); - index = index === 0 ? this.documents.length - 1 : index - 1; - this.activeDocument = this.documents[index]; - this._updateContent(); - }, - /** - * @private - */ - _reset: function () { - this.scale = 1; - this.dragStartX = this.dragstopX = 0; - this.dragStartY = this.dragstopY = 0; - }, - /** - * Render the active attachment - * - * @private - */ - _updateContent: function () { - this.$('.cmis_web_viewer_content').html(QWeb.render('cmis_web.DocumentViewer.Content', { - widget: this - })); - this.$('.cmis_web_viewer_img').on("load", _.bind(this._onImageLoaded, this)); - this._reset(); - }, - /** - * Zoom in/out image by provided scale - * - * @private - * @param {integer} scale - */ - _zoom: function (scale) { - if (scale > 0.5) { - this.$('.cmis_web_viewer_img').css('transform', 'scale3d(' + scale + ', ' + scale + ', 1)'); - this.scale = scale; - } - }, - /** - * Get CSS transform property based on scale and angle - * - * @private - * @param {float} scale - * @param {float} angle - */ - _getTransform: function(scale, angle) { - return 'scale3d(' + scale + ', ' + scale + ', 1) rotate(' + angle + 'deg)'; - }, - /** - * Rotate image clockwise by provided angle - * - * @private - * @param {float} angle - */ - _rotate: function (angle) { - this._reset(); - var new_angle = (this.angle || 0) + angle; - this.$('.cmis_web_viewer_img').css('transform', this._getTransform(this.scale, new_angle)); - this.$('.cmis_web_viewer_img').css('max-width', new_angle % 180 !== 0 ? $(document).height() : '100%'); - this.$('.cmis_web_viewer_img').css('max-height', new_angle % 180 !== 0 ? $(document).width() : '100%'); - this.angle = new_angle; - }, - - // -------------------------------------------------------------------------- - // Handlers - // -------------------------------------------------------------------------- - - /** - * @private - * @param {MouseEvent} e - */ - _onClose: function (e) { - e.preventDefault(); - this.$el.modal('hide'); - }, - /** - * When popup close complete destroyed modal even DOM footprint too - * @private - */ - _onDestroy: function () { - if (this.isDestroyed()) { - return; - } - this.$el.modal('hide'); - this.$el.remove(); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onDownload: function (e) { - e.preventDefault(); - window.open(this.activeDocument.url); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onDrag: function (e) { - e.preventDefault(); - if (this.enableDrag) { - var $image = this.$('.cmis_web_viewer_img'); - var $zoomer = this.$('.cmis_web_viewer_zoomer'); - var top = $image.prop('offsetHeight') * this.scale > $zoomer.height() ? e.clientY - this.dragStartY : 0; - var left = $image.prop('offsetWidth') * this.scale > $zoomer.width() ? e.clientX - this.dragStartX : 0; - $zoomer.css("transform", "translate3d("+ left +"px, " + top + "px, 0)"); - } - }, - /** - * @private - * @param {MouseEvent} e - */ - _onEndDrag: function (e) { - e.preventDefault(); - if (this.enableDrag) { - this.enableDrag = false; - this.dragstopX = e.clientX - this.dragStartX; - this.dragstopY = e.clientY - this.dragStartY; - } - }, - /** - * On click of image do not close modal so stop event propagation - * - * @private - * @param {MouseEvent} e - */ - _onImageClicked: function (e) { - e.stopPropagation(); - }, - /** - * Remove loading indicator when image loaded - * @private - */ - _onImageLoaded: function () { - this.$('.cmis_web_loading_img').hide(); - }, - /** - * Move next previous attachment on keyboard right left key - * - * @private - * @param {KeyEvent} e - */ - _onKeydown: function (e){ - switch (e.which) { - case $.ui.keyCode.RIGHT: - e.preventDefault(); - this._next(); - break; - case $.ui.keyCode.LEFT: - e.preventDefault(); - this._previous(); - break; - } - }, - /** - * @private - * @param {MouseEvent} e - */ - _onNext: function (e) { - e.preventDefault(); - this._next(); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onPrevious: function (e) { - e.preventDefault(); - this._previous(); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onPrint: function (e) { - e.preventDefault(); - var src = this.$('.cmis_web_viewer_img').prop('src'); - var script = QWeb.render('PrintImage', { - src: src - }); - var printWindow = window.open('about:blank', "_new"); - printWindow.document.open(); - printWindow.document.write(script); - printWindow.document.close(); - }, - /** - * Zoom image on scroll - * - * @private - * @param {MouseEvent} e - */ - _onScroll: function (e) { - var scale; - if (e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0) { - scale = this.scale + SCROLL_ZOOM_STEP; - this._zoom(scale); - } else { - scale = this.scale - SCROLL_ZOOM_STEP; - this._zoom(scale); - } - }, - /** - * @private - * @param {MouseEvent} e - */ - _onStartDrag: function (e) { - e.preventDefault(); - this.enableDrag = true; - this.dragStartX = e.clientX - (this.dragstopX || 0); - this.dragStartY = e.clientY - (this.dragstopY || 0); - }, - /** - * On click of video do not close modal so stop event propagation - * and provide play/pause the video instead of quitting it - * - * @private - * @param {MouseEvent} e - */ - _onVideoClicked: function (e) { - e.stopPropagation(); - var videoElement = e.target; - if (videoElement.paused) { - videoElement.play(); - } else { - videoElement.pause(); - } - }, - /** - * @private - * @param {MouseEvent} e - */ - _onRotate: function (e) { - e.preventDefault(); - this._rotate(90); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onZoomIn: function (e) { - e.preventDefault(); - var scale = this.scale + ZOOM_STEP; - this._zoom(scale); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onZoomOut: function (e) { - e.preventDefault(); - var scale = this.scale - ZOOM_STEP; - this._zoom(scale); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onZoomReset: function (e) { - e.preventDefault(); - this.$('.cmis_web_viewer_zoomer').css("transform", ""); - this._zoom(1); - }, -}); -return DocumentViewer; -}); diff --git a/cmis_web/static/src/js/field_utils.js b/cmis_web/static/src/js/field_utils.js deleted file mode 100644 index 4fa6b402..00000000 --- a/cmis_web/static/src/js/field_utils.js +++ /dev/null @@ -1,15 +0,0 @@ -odoo.define('cmis_web.field_utils', function (require) { - "use strict"; - const field_utils = require('web.field_utils') - const FieldCmisDocument = require('cmis_web.form_widgets').FieldCmisDocument - - function format(value, field, options) { - value = typeof value === 'string' ? value : ''; - if (options && options.escape) { - value = _.escape(value); - } - return value; - } - - field_utils.format.cmis_document = format; -}); diff --git a/cmis_web/static/src/js/form_widgets.js b/cmis_web/static/src/js/form_widgets.js deleted file mode 100644 index bacd57e1..00000000 --- a/cmis_web/static/src/js/form_widgets.js +++ /dev/null @@ -1,2504 +0,0 @@ -/*--------------------------------------------------------- - + * Odoo cmis_web - + * Author Laurent Mignon 2016 Acsone SA/NV - + * License in __openerp__.py at root level of the module - + *--------------------------------------------------------- - +*/ - -odoo.define('cmis_web.form_widgets', function (require) { - "use strict"; - - - var core = require('web.core'); - var registry = require('web.field_registry'); - var basicFields = require('web.basic_fields'); - var time = require('web.time'); - var Dialog = require('web.Dialog'); - var framework = require('web.framework'); - var DocumentViewer = require('cmis_web.DocumentViewer') - var crash_manager = require('web.CrashManager'); - - var _t = core._t; - var QWeb = core.qweb; - - Dialog.include({ - check_validity: function () { - if (this.el.checkValidity()) { - return true; - } - else { - // Use pseudo HMLT5 submit to display validation errors - $('').hide().appendTo(this.$el).click().remove(); - } - }, - }); - - var CmisRenameContentDialog = Dialog.extend({ - template: 'CmisRenameContentDialog', - init: function (parent, cmisObject) { - var self = this; - var options = { - buttons: [ - { - text: _t("Rename"), - classes: "btn-primary", - click: function (e) { - e.stopPropagation(); - if (self.check_validity()) { - self.on_click_process(); - } - } - }, - { - text: _t("Cancel"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - - ], - close: function () { - self.close(); - } - }; - this._super(parent, options); - this.cmisObject = cmisObject; - this.cmisSession = parent.cmis_session; - }, - - start: function () { - this.$newName = this.$el.find('#new-name'); - this.$newName.val(this.cmisObject.name); - this.$newName.select(); - }, - on_click_process: function () { - var self = this; - var newName = this.$newName.val() - if (newName !== this.cmisObject.name && this.check_validity()) { - this.cmisSession - .updateProperties(this.cmisObject.objectId, {'cmis:name': newName}) - .ok(function (cmisObject) { - self.trigger_up('reload'); - self.$el.parents('.modal').modal('hide'); - }); - } - } - }); - - var CmisDuplicateDocumentResolver = Dialog.extend({ - template: 'CmisDuplicateDocumentResolver', - init: function (parent, parent_cmisobject, file) { - var self = this; - var options = { - buttons: [ - { - text: _t("Process"), - classes: "btn-primary", - click: function (e) { - e.stopPropagation(); - if (self.check_validity()) { - self.on_click_process(); - } - } - }, - { - text: _t("Cancel"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - - ], - close: function () { - self.close(); - }, - title: file.name + _t(" already exists") - }; - this._super(parent, options); - this.parent_cmisobject = parent_cmisobject; - this.cmis_session = parent.cmis_session; - this.file = file; - this.new_filename = ''; - this.original_objectId = ''; - }, - - renderElement: function () { - this._super(); - this.$new_filename = this.$el.find('#new-filename'); - this.$new_filename.val(this.new_filename); - }, - - escape_query_param: function(param){ - param = param.replace(new RegExp("'", "g"), "\\'"); - return param; - }, - - /** - * Method called between @see init and @see start. Performs asynchronous - * calls required by the rendering and the start method. - */ - willStart: function () { - var self = this; - var re = /(?:\.([^.]+))?$/; - var parts = re.exec(this.file.name); - var name_without_ext = this.file.name.slice(0, -parts[1].length - 1); - var ext = parts[1]; - // looks for an alternate filename - var dfd1 = $.Deferred(); - this.cmis_session.query('' + - "SELECT cmis:name FROM cmis:document WHERE " + - "IN_FOLDER('" + this.parent_cmisobject.objectId + - "') AND cmis:name like '" + self.escape_query_param(name_without_ext) + "-%." + ext + "'") - .ok(function (data) { - var cpt = data.results.length; - var filenames = _.map( - data.results, - function (item) { - return item.succinctProperties['cmis:name'][0]; - }); - while (true) { - self.new_filename = name_without_ext + '-' + - cpt + '.' + ext; - if (_.contains(filenames, self.new_filename)) { - cpt += 1; - } else { - break; - } - } - dfd1.resolve(); - }) - .notOk(function (error) { - self.getParent().on_cmis_error(error); - dfd1.reject(error); - }); - // get original document - var dfd2 = $.Deferred(); - this.cmis_session.query('' + - "SELECT cmis:objectId FROM cmis:document WHERE " + - "IN_FOLDER('" + this.parent_cmisobject.objectId + - "') AND cmis:name = '" + self.escape_query_param(this.file.name) + "'") - .ok(function (data) { - self.original_objectId = data.results[0].succinctProperties['cmis:objectId']; - dfd2.resolve(); - }) - .notOk(function (error) { - self.getParent().on_cmis_error(error); - dfd2.reject(error); - }); - return $.when(this._super.apply(this, arguments), dfd1.promise(), dfd2.promise()); - }, - - on_click_process: function () { - var self = this; - var rename = this.$el.find("input:radio[name='duplicate-radios']:checked").val() === "rename"; - if (rename) { - this.cmis_session - .createDocument(this.parent_cmisobject.objectId, this.file, {'cmis:name': this.$new_filename.val()}, this.file.mimeType) - .ok(function (new_cmisobject) { - self.getParent().trigger('cmis_node_created', [new_cmisobject]); - self.$el.parents('.modal').modal('hide'); - }); - } else { - var major = this.$el.find("#new-version-type").val() === "major"; - var comment = this.$el.find('#comment').val(); - self.cmis_session.checkOut(self.original_objectId) - .ok(function (checkedOutNode) { - self.cmis_session - .checkIn(checkedOutNode.succinctProperties['cmis:objectId'], major, {}, self.file, comment) - .ok(function (data) { - // after checkin the working copy must be deleted (self.data) - // the date received into the response is the new version - // created - self.getParent().trigger('cmis_node_deleted', [self.original_objectId]); - self.$el.parents('.modal').modal('hide'); - }); - }); - } - } - }); - - var CmisCreateFolderDialog = Dialog.extend({ - template: 'CmisCreateFolderDialog', - init: function (parent, parent_cmisobject) { - var self = this; - var options = { - buttons: [ - { - text: _t("Create"), - classes: "btn-primary", - click: function () { - if (self.check_validity()) { - self.on_click_create(); - } - } - }, - { - text: _t("Close"), - click: function () { - self.$el.parents('.modal').modal('hide'); - } - }, - ], - close: function () { - self.close(); - }, - title: (_t("Create Folder ")) - }; - this._super(parent, options); - this.parent_cmisobject = parent_cmisobject; - }, - - on_click_create: function () { - var self = this; - var input = this.$el.find("input[type='text']")[0]; - framework.blockUI(); - var cmis_session = this.getParent().cmis_session; - cmis_session - .createFolder(this.parent_cmisobject.objectId, input.value) - .ok(function (new_cmisobject) { - framework.unblockUI(); - self.getParent().trigger('cmis_node_created', [new_cmisobject]); - self.$el.parents('.modal').modal('hide'); - }); - }, - - close: function () { - this._super(); - } - }); - - var CmisCreateDocumentDialog = Dialog.extend({ - template: 'CmisCreateDocumentDialog', - events: { - 'change .btn-file :file': 'on_file_change' - }, - - init: function (parent, parent_cmisobject) { - var self = this; - var options = { - buttons: [ - { - text: _t("Add"), - classes: "btn-primary", - click: function (e) { - e.stopPropagation(); - if (self.check_validity()) { - self.on_click_create(); - } - } - }, - { - text: _t("Close"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - - ], - close: function () { - self.close(); - }, - title: _t("Create Documents "), - }; - this._super(parent, options); - }, - - on_file_change: function (e) { - var input = $(e.target), - numFiles = input.get(0).files ? input.get(0).files.length : 1, - label = input.val().replace(/\\/g, '/').replace(/.*\//, ''), - log = numFiles > 1 ? numFiles + ' files selected' : label; - var input_text = input.closest('.input-group').find(':text'); - input_text.val(log); - }, - - on_click_create: function () { - var self = this, - input = this.$el.find("input[type='file']")[0], - numFiles = input.files ? input.files.length : 1; - var processedFiles = []; - if (numFiles > 0) { - framework.blockUI(); - } - var parent = this.getParent(); - var cmis_session = parent.cmis_session; - _.each(input.files, function (file, index, list) { - cmis_session - .createDocument(parent.dislayed_folder_cmisobject.objectId, file, {'cmis:name': file.name}, file.mimeType) - .ok(function (data) { - processedFiles.push(data); - if (processedFiles.length == numFiles) { - framework.unblockUI(); - parent.trigger('cmis_node_created', [processedFiles]); - } - }); - }, self); - self.$el.parents('.modal').modal('hide'); - }, - - close: function () { - this._super(); - } - }); - - var CmisLinkDocumentDialog = Dialog.extend({ - template: 'CmisLinkDocumentDialog', - - events: { - 'click .btn-search': 'on_click_search', - 'keypress .search-input': 'on_keypress_search_input', - }, - - init: function (parent, parentCmisObject) { - var self = this; - var options = { - buttons: [ - { - text: _t("Link"), - classes: "btn-primary", - click: function (e) { - e.stopPropagation(); - if (self.check_validity()) { - self.on_click_link(); - } - } - }, - { - text: _t("Close"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - - ], - close: function () { - self.close(); - }, - title: _t("Link Document"), - }; - this._super(parent, options); - this.parent = parent; - this.cmis_src_folder_fname = parent.nodeOptions.link_document_src_folders; - this.search_enabled = ( - this.cmis_src_folder_fname !== undefined - && this.parent.recordData.hasOwnProperty(this.cmis_src_folder_fname) - && this.parent.recordData[this.cmis_src_folder_fname] !== "[]" - ); - this.search_results = []; - }, - - wrap_cmis_object: function (cmisObject) { - if (_.has(cmisObject, 'object')) { - cmisObject = cmisObject.object; - } - return new CmisObjectWrapper(this, cmisObject, this.parent.cmis_session); - }, - - start: function() { - this._super.apply(this, arguments); - this.$search_input = this.$el.find('.search-input'); - this.$document_list = this.$el.find('.document-list'); - this.$identifier = this.$el.find("input[id='identifier']") - this.on_click_search(); - this.add_link_events(); - }, - - get_folder_cmis_ids: function() { - let record = this.parent.recordData; - return JSON.parse(record[this.cmis_src_folder_fname]); - }, - - on_keypress_search_input: function (e) { - if (e.keyCode === 13) { - e.preventDefault(); - e.stopPropagation(); - this.on_click_search(); - } - }, - - on_click_search: function (e) { - let self = this; - let $input = self.$search_input; - let search_value = $input.val(); - if (search_value.length === 1) { - alert(_t( - "The search criteria should at least contains 2 characters. " + - "Or empty if you want to see all documents." - )) - return false; - } - - let parent = self.parent; - let cmis_session = parent.cmis_session; - let cmis_folders = self.get_folder_cmis_ids() - - let tree_where_clauses = []; - _.each(cmis_folders, function(cmis_folder) { - tree_where_clauses.push("IN_TREE('" + cmis_folder + "')"); - }); - - let fnames_to_search = [ - "cmis:name", - "cmis:description", - ] - let text_clauses = []; - if (search_value.length > 0) { - text_clauses.push("CONTAINS('"+ search_value +"')"); - } - _.each(fnames_to_search, function(fname_to_search) { - text_clauses.push(fname_to_search + " like '%" + search_value + "%'"); - }); - - let query = ( - "SELECT * FROM cmis:document " + - "WHERE (" + tree_where_clauses.join(" OR ") + ") "+ - " AND " + - "(" + - text_clauses.join(" OR ") + - ")" - ) - - return cmis_session.query(query).ok(function (search_result) { - let results = []; - _.each(search_result.results, function(result) { - results.push(self.wrap_cmis_object(result)); - }) - self.search_results = results; - self.display_document_list(); - }); - }, - - display_document_list: function() { - let $document_list = QWeb.render("CmisLinkDocumentDialogDocumentList", { - widget: this, - }); - this.$document_list.html($document_list); - this.add_link_events(); - }, - - add_link_events: function() { - let self = this; - this.$document_list.find('.link-button').click(function(e) { - let $target = $(e.target); - let $tr = $target.closest('tr'); - let cmis_object_id = $tr.data('cmis-id'); - self.$identifier.val(cmis_object_id); - if (self.check_validity()) { - self.on_click_link(); - } - }); - }, - - on_click_link: function () { - var objectId = this.$el.find("input[id='identifier']").val(); - var parent = this.getParent(); - parent.trigger('cmis-link-document', objectId); - this.close(); - }, - - close: function () { - this._super(); - } - }); - - var CmisAddDocumentDialog = Dialog.extend({ - template: 'CmisAddDocumentDialog', - events: { - 'change .btn-file :file': 'on_file_change' - }, - - init: function (parent) { - var self = this; - var options = { - buttons: [ - { - - text: _t("Add"), - classes: "btn-primary", - click: function (e) { - e.stopPropagation(); - if (self.check_validity()) { - self.on_click_create(); - } - } - }, - { - text: _t("Close"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - - ], - close: function () { - self.close(); - }, - title: _t("Create Documents "), - }; - this._super(parent, options); - }, - - on_file_change: function (e) { - var input = $(e.target), - numFiles = input.get(0).files ? input.get(0).files.length : 1, - label = input.val().replace(/\\/g, '/').replace(/.*\//, ''), - log = numFiles > 1 ? numFiles + ' files selected' : label; - var input_text = input.closest('.input-group').find(':text'); - input_text.val(log); - }, - - on_click_create: function () { - var self = this, - input = this.$el.find("input[type='file']")[0], - numFiles = input.files ? input.files.length : 1; - if (numFiles > 0) { - framework.blockUI(); - } - var parent = this.getParent(); - framework.unblockUI(); - parent.trigger('cmis-add-document', input.files); - self.$el.parents('.modal').modal('hide'); - }, - - close: function () { - this._super(); - } - }); - - var SingleFileUpload = Dialog.extend({ - events: { - 'change .btn-file :file': 'on_file_change' - }, - - init: function (parent, cmisObjectWrapped, options) { - var self = this; - var btnOkTitle = _t('OK'); - if (!_.isUndefined(options) && _.has(options, 'btnOkTitle')) { - btnOkTitle = options.btnOkTitle; - } - options = _.defaults(options || {}, { - buttons: [ - { - text: btnOkTitle, - classes: "btn-primary", - click: function (e) { - e.stopPropagation(); - if (self.check_validity()) { - self.on_click_ok(); - } - } - }, - { - text: _t("Close"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - ], - close: function () { - self.close(); - } - }); - this._super(parent, options); - this.data = cmisObjectWrapped; - }, - - on_file_change: function (e) { - var input = $(e.target), - label = input.val().replace(/\\/g, '/').replace(/.*\//, ''), - input_text = input.closest('.input-group').find(':text'); - input_text.val(label); - }, - - onDestroy: function (callback) { - this._onDestroy = callback; - }, - - on_click_ok: function () { - var self = this; - var input = this.$el.find("input[type='file']")[0] - var numFiles = input.files ? input.files.length : 1; - if (numFiles == 0) { - this.close(); - } - var file = input.files[0]; - var fileName = file.name; - framework.blockUI(); - this._do_upload(file, fileName).then(function (data) { - framework.unblockUI(); - if (!_.isUndefined(data)) { - self.getParent().trigger('cmis_node_content_updated', [data]); - } - self.$el.parents('.modal').modal('hide'); - }); - }, - - /** - * This method must be implemented into concrete dialog an return a promise - * The promise must be resolved with updated cmisObject - */ - _do_upload: function (file, filename) { - }, - - close: function () { - this._super(); - }, - - destroy: function () { - const onDestroy = this._onDestroy; - this._super(); - if (typeof (onDestroy) === "function") { - onDestroy(); - } - } - }); - - var CmisUpdateContentStreamDialog = SingleFileUpload.extend({ - template: 'CmisUpdateContentStreamView', - - init: function (parent, cmisObjectWrapped) { - var self = this; - var options = { - btnOkTitle: _t("Update content"), - title: _t("Update content of ") + cmisObjectWrapped.name, - }; - this._super(parent, cmisObjectWrapped, options); - }, - - _do_upload: function (file, fileName) { - var dfd = $.Deferred(); - this.data.cmis_session - .setContentStream(this.data.objectId, file, true, fileName) - .ok(function (data) { - dfd.resolve(data); - }); - return dfd.promise(); - }, - }); - - var CmisCheckinDialog = SingleFileUpload.extend({ - template: 'CmisCheckinView', - - init: function (parent, cmisObjectWrapped, useFileName) { - var self = this; - var options = { - btnOkTitle: _t("Import new version"), - title: _t("Import new version of ") + cmisObjectWrapped.name, - }; - this.useFileName = useFileName; - this._super(parent, cmisObjectWrapped, options); - }, - - _do_upload: function (file, fileName) { - var self = this; - var dfd = $.Deferred(); - var major = this.$el.find("input:radio[name='version-radios']:checked").val() === "major"; - var comment = this.$el.find('#comment').val(); - var name = this.useFileName ? fileName : {}; - this.data.cmis_session - .checkIn(this.data.objectId, major, name, file, comment) - .ok(function (data) { - // after checkin the working copy must be deleted (self.data) - // the date received into the response is the new version - // created - self.getParent().trigger('cmis_node_deleted', [self.data.cmis_object]); - dfd.resolve(data); - }); - return dfd.promise(); - }, - }); - - var CmisVersionsHistoryDialog = Dialog.extend({ - template: 'CmisVersionsHistoryDialog', - events: { - 'change .btn-file :file': 'on_file_change' - }, - - init: function (parent, versions) { - var self = this; - this.versions = versions; - var options = { - buttons: [ - { - text: _t("Close"), - click: function (e) { - e.stopPropagation(); - self.$el.parents('.modal').modal('hide'); - } - }, - - ], - close: function () { - self.close(); - }, - title: _t("Document history"), - }; - this._super(parent, options); - }, - - start: function() { - this.register_document_action_events(); - return this._super.apply(this, arguments); - }, - - register_document_action_events: function () { - var self = this; - this.$el.find('.version-download').on('click', function (e) { - e.stopPropagation(); - self.on_click_download(e); - }); - this.$el.find('.version-preview').on('click', function (e) { - e.stopPropagation(); - self.on_click_preview(e); - }); - }, - - _get_version_url: function (version) {}, - - on_click_preview: function (e) { - var objectId = e.target.attributes.value.nodeValue; - var version = this.versions.find((item) => item.objectId === objectId); - var url = this._get_version_url(version); - if (url) { - window.open(url); - }; - }, - - on_click_download: function(e) { - var objectId = e.target.attributes.value.nodeValue; - var version = this.versions.find((item) => item.objectId === objectId); - if (version) { - window.open(version.url); - } - }, - - close: function () { - this._super(); - } - }); - - var CmisObjectWrapper = core.Class.extend({ - - init: function (parent, cmis_object, cmis_session) { - this.parent = parent; - this.cmis_object = cmis_object; - this.cmis_session = cmis_session; - this.parse_object(cmis_object); - }, - - _clone: function () { - return new CmisObjectWrapper(this.parent, this.cmis_object, this.cmis_session); - }, - - parse_object: function (cmis_object) { - this.name = this.getSuccinctProperty('cmis:name', cmis_object); - this.mimetype = this.getSuccinctProperty('cmis:contentStreamMimeType', cmis_object); - this.baseTypeId = this.getSuccinctProperty('cmis:baseTypeId', cmis_object); - this.title = this.getSuccinctProperty('cm:title', cmis_object) || ''; - this.description = this.getSuccinctProperty('cmis:description', cmis_object); - this.lastModificationDate = this.getSuccinctProperty('cmis:lastModificationDate', cmis_object); - this.creationDate = this.getSuccinctProperty('cmis:creationDate', cmis_object); - this.lastModifiedBy = this.getSuccinctProperty('cmis:lastModifiedBy', cmis_object); - this.objectId = this.getSuccinctProperty('cmis:objectId', cmis_object); - this.versionSeriesId = this.getSuccinctProperty('cmis:versionSeriesId', cmis_object); - this.versionLabel = this.getSuccinctProperty('cmis:versionLabel'); - this.url = this.cmis_session.getContentStreamURL(this.objectId, 'attachment'); - this.isCheckedOut = !!this.getSuccinctProperty("cmis:versionSeriesCheckedOutId"); - this.allowableActions = cmis_object.allowableActions; - this.renditions = cmis_object.renditions; - }, - - getSuccinctProperty: function (property, cmis_object) { - cmis_object = cmis_object || this.cmis_object; - return cmis_object.succinctProperties[property]; - }, - - _get_css_class: function () { - if (this.baseTypeId === 'cmis:folder') { - return 'fa fa-folder cmis-folder'; - } - - if (this.mimetype) { - switch (this.mimetype) { - case 'application/pdf': - return 'fa fa-file-pdf-o'; - case 'text/plain': - return 'fa fa-file-text-o'; - case 'text/html': - return 'fa fa-file-code-o'; - case 'application/json': - return 'fa fa-file-code-o'; - case 'application/gzip': - return 'fa fa-file-archive-o'; - case 'application/zip': - return 'fa fa-file-archive-o'; - case 'application/octet-stream': - return 'fa fa-file-o'; - } - switch (this.mimetype.split('/')[0]) { - case 'image': - return 'fa fa-file-image-o'; - case 'audio': - return 'fa fa-file-audio-o'; - case 'video': - return 'fa fa-file-video-o'; - } - } - if (this.baseTypeId === 'cmis:document') { - return 'fa fa-file-o'; - } - return 'fa fa-fw'; - }, - - /** fName - * return the cmis:name formatted to be rendered in ta datatable cell - * - **/ - fName: function () { - var cls = this._get_css_class(); - var val = "
" + this.name; - val = val + "
"; - if (this.getSuccinctProperty('cmis:isVersionSeriesCheckedOut')) { - val = val + "
" + _t('By:') + ' ' + this.getSuccinctProperty('cmis:versionSeriesCheckedOutBy') + '
'; - } - return val; - }, - - /** fLastModificationDate - * return the cmis:mastModificationDate formatted to be rendered in ta datatable cell - * - **/ - fLastModificationDate: function () { - return this.format_cmis_timestamp(this.lastModificationDate); - }, - - /** - * Format cmis object creation date - * @returns the cmis:creationDate formatted to be rendered in a datatable cell - * - **/ - fCreationDate: function() { - return this.format_cmis_timestamp(this.creationDate); - }, - - fDetails: function () { - return '
'; - }, - - format_cmis_timestamp: function (cmis_timestamp) { - if (cmis_timestamp) { - var d = new Date(cmis_timestamp); - var l10n = _t.database.parameters; - var date_format = time.strftime_to_moment_format(l10n.date_format); - var time_format = time.strftime_to_moment_format(l10n.time_format); - var value = moment(d); - return value.format(date_format + ' ' + time_format); - } - return ''; - }, - - getActionsContext: function () { - var ctx = {object: this}; - _.map(this.cmis_object.allowableActions, function (value, actionName) { - ctx[actionName] = value; - }); - ctx['canPreview'] = ctx['canGetContentStream']; // && this.mimetype === 'application/pdf'; - ctx['isFolder'] = this.baseTypeId == 'cmis:folder'; - return ctx - }, - - /** - * Content actions - * - * render the list of available actions - */ - fContentActions: function () { - var ctx = this.getActionsContext(); - return QWeb.render("CmisContentActions", ctx); - }, - - documentActions: function () { - var ctx = this.getActionsContext(); - return QWeb.render("CmisDocumentActions", ctx) - }, - - get_content_url: function () { - return this.cmis_session.getContentStreamURL(this.objectId, 'inline'); - }, - - get_preview_url: function () { - var rendition = _.findWhere(this.renditions, {mimeType: 'application/pdf'}); - if (this.mimetype === 'application/pdf') { - return this.get_content_url(); - } else if (rendition) { - return this.cmis_session.getContentStreamURL(rendition['streamId']); - } - return null; - }, - - get_preview_type: function () { - if (this.baseTypeId === 'cmis:folder') { - return undefined; - } - if (this.mimetype.match("(image)")) { - return 'image'; - } - if (this.mimetype.match("(video)")) { - return 'video'; - } - // here we hope that alfresco is able to render the document as pdf - return "pdf"; - }, - - - /** - * Refresh the information by reloading data from the server - * The method return a deferred called once the information are up to date - */ - refresh: function () { - var self = this; - var dfd = $.Deferred() - var options = DEFAULT_CMIS_OPTIONS; - var oldValue = this._clone(); - this.cmis_session.getObject( - this.objectId, - 'latest', options).ok(function (data) { - self.parse_object(data); - self.parent.trigger('wrapped_cmis_node_reloaded', oldValue, self); - dfd.resolve(self); - }); - return dfd.promise(); - }, - - }); - - var DEFAULT_CMIS_OPTIONS = { - includeAllowableActions: true, - renditionFilter: 'application/pdf', - } - - /** - * A Mixin class defining common methods used by Cmis widgets - */ - var CmisMixin = { - - init: function () { - this.cmis_session_initialized = $.Deferred(); - this.cmis_config_loaded = $.Deferred(); - this.cmis_location = null; - this.cmis_backend_id = null; - this.cmis_backend_fields = ['id', 'location']; - }, - - /** - * Load CMIS settings from Odoo server - */ - load_cmis_config: function () { - this.bind_cmis_config(this.backend); - this.on_cmis_config_loaded(this.backend); - }, - - /** - * Parse the result of the call to the server to retrieve the CMIS settings - */ - bind_cmis_config: function (result) { - if (result.backend_error) { - this.do_warn( - _t("CMIS Backend Config Error"), - result.backend_error, - true); - return; - } - this.cmis_location = result.location; - this.cmis_backend_id = result.id; - }, - - on_cmis_config_loaded: function (result) { - this.cmis_config_loaded.resolve(); - }, - - /** - * Initialize the CmisJS session and register handlers for warnings and errors - * occuring when calling the CMIS DMS - */ - init_cmis_session: function () { - var self = this; - $.when(this.cmis_config_loaded).done(function () { - self.cmis_session = cmis.createSession(self.cmis_location); - self.cmis_session.setGlobalHandlers(self.on_cmis_error, self.on_cmis_error); - self.cmis_session_initialized.resolve(); - self.cmis_session.setCharacterSet(document.characterSet); - }); - }, - - /** - * Load the default repository if required. - * token or credentils must already be set. - * At this stage the widget doesn't support multi repositories but - * if we want to get a chance to put a token based on the data from - * the odoo model, this method can only be called once the values - * are provided by the form controller ant we load the root folder for - * exemple (set_root_folder_id). - * Loading the repositories is required before calling to others cmis - * methods - */ - load_cmis_repositories: function () { - var dfd = $.Deferred(); - var self = this; - if (this.cmis_session.repositories) { - return dfd.resolve(); - } else { - self.cmis_session - .loadRepositories() - .ok(function (data) { - dfd.resolve(); - }) - .notOk(function (error) { - self.on_cmis_error(error); - dfd.reject(error); - }); - } - return dfd.promise(); - }, - - /** - * Method called by the cmis session in case of error or warning - */ - on_cmis_error: function (error) { - framework.unblockUI(); - if (error) { - if (error.type == 'application/json') { - error = JSON.parse(error.text); - new Dialog(this, { - size: 'medium', - title: _t("CMIS Error "), - $content: $('
').html(QWeb.render('CMISSession.warning', {error: error})) - }).open(); - } else { - new Dialog(this, { - size: 'medium', - title: _t("CMIS Error"), - subtitle: error.statusText, - $content: $('
').html(error.text) - }).open(); - } - } - }, - - /** - * Wrap a - */ - wrap_cmis_object: function (cmisObject) { - if (_.has(cmisObject, 'object')) { - cmisObject = cmisObject.object; - } - return new CmisObjectWrapper(this, cmisObject, this.cmis_session); - }, - - wrap_cmis_objects: function (cmisObjects) { - var self = this; - return _.chain(cmisObjects) - .map(function (item) { - return self.wrap_cmis_object(item) - }) - .uniq(function (wrapped) { - return wrapped.objectId - }) - .value() - }, - }; - - var FieldCmisDocument = basicFields.FieldChar.extend(CmisMixin, { - template: "FieldCmisDocument", widget_class: 'field_cmis_document', - - init: function (parent, name, record, options) { - this._super.apply(this, arguments); - CmisMixin.init.call(this); - this.backend = this.field.backend; - this.formatType = 'char'; - this.document = {}; - - this.on('cmis-add-document', this, this.onAddDocument); - this.on('cmis-link-document', this, this.onLinkDocument); - this.on('cmis_node_content_updated', this, this.onDocumentUpdated); - }, - - willStart: function () { - var self = this; - this.load_cmis_config(); - this.init_cmis_session(); - this.cmisSessionReady = Promise.all([ - this.cmis_session_initialized, - this.load_cmis_repositories() - ]); - return new Promise(function(resolve) { - self.cmisSessionReady.then(function() { - if (self.value) { - self._get_document(self.value).catch(function(error) { - self.value = "__error__"; - resolve({}); - }).then(function (document) { - if (!!document) { - self.document = self.wrap_cmis_object(document); - resolve(document); - } - }); - } else { - resolve({}); - } - }); - }); - }, - - _render: function () { - this._super.apply(this, arguments); - if (this.value === "__error__") { - this._renderGetError(); - } else if (this.value && this.value !== "empty" && this.document) { - this._renderCmisDocument(this.document); - } else { - this._renderNoDocument(); - } - - this.$el.find('.dropdown-menu').off('mouseleave'); - // hide the dropdown menu on mouseleave - this.$el.find('.dropdown-menu').on('mouseleave', function (e) { - if ($(e.target).is(':visible')) { - $(e.target).closest('.btn-group').find('.dropdown-toggle[aria-expanded="true"]').trigger('click').blur(); - } - }); - // hide the dropdown menu on link clicked - this.$el.find('.dropdown-menu a').on('click', function (e) { - if ($(e.target).is(':visible')) { - $(e.target).closest('.btn-group').find('.dropdown-toggle[aria-expanded="true"]').trigger('click').blur(); - } - }); - }, - - start: function () { - if (!this.value) { - // This is a bit of a hack, but there is no hook that allows removing - // the `o_field_empty` class on a widget with no value. - this.value = "empty"; - } - return this._super.apply(this, arguments); - }, - - _renderCmisDocument: function (document) { - var $cmisDoc = QWeb.render("CmisDocumentReadOnly", {object: document}); - this.$el.html($cmisDoc); - this.register_document_action_events(); - }, - - _renderNoDocument: function () { - var $cmisDoc = QWeb.render("CmisNoDocumentActions", {}); - this.$el.html($cmisDoc); - this.register_no_document(); - }, - - _renderGetError: function() { - this.$el.empty(); - this.$el.append($('

').text(_t("Unable to retrieve the document. The resource might have been deleted in the CMIS."))) - }, - - _get_document: function (objectId) { - var self = this; - return new Promise(function (resolve, reject) { - self.cmis_session.getObject(objectId, "latest", DEFAULT_CMIS_OPTIONS) - .ok(function (document) { - resolve(document); - }).notOk(function (error) { - reject(error); - }); - }); - }, - - onAddDocument: function (files) { - var self = this; - this.cmis_config_loaded - .then(() => self._getDocumentsFromFiles(files)) - .then((documents) => self._uploadDocumentsToCreate(documents)) - .catch((error) => false) - .then((result) => result ? self.trigger_up('reload') : false); - }, - - onLinkDocument: function(objectId) { - var self = this; - this.cmis_config_loaded - .then(() => self.update_document_field(objectId)); - }, - - onDocumentUpdated: function (document) { - this.value = document.objectId; - this.trigger_up('reload'); - }, - - _uploadDocumentsToCreate: function (documents) { - return this._rpc({ - route: '/web/cmis/field/create_document_value', params: { - 'model_name': this.model, 'res_id': this.res_id, 'field_name': this.name, 'documents': documents - } - }); - }, - - _getDocumentsFromFiles: function (files) { - return Promise.all(_.map(files, function (file) { - return new Promise(function (resolve, reject) { - var reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = function () { - resolve({ - name: file.name, mimetype: file.type, data: reader.result - }); - }; - reader.onerror = error => reject(error); - }); - })); - }, - - onClickAddDocument: function () { - var dialog = new CmisAddDocumentDialog(this); - dialog.open(); - }, - - onClickLinkDocument: function () { - var dialog = new CmisLinkDocumentDialog(this); - dialog.open(); - }, - - on_click_preview: function () { - var documentViewer = new DocumentViewer(this, this.document); - documentViewer.appendTo($('body')); - }, - - update_document_field: function (objectId) { - var re = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; - var ok = re.exec(objectId); - if (!ok) { - return {"error": "invalid_object_id"}; - }; - var changes = {}; - changes[this.name] = objectId; - this.trigger_up('field_changed', { - dataPointID: this.dataPointID, changes: changes, - }); - }, - - on_click_download: function () { - this.do_download(this.document); - }, - - do_download: function (cmisObjectWrapped) { - window.open(cmisObjectWrapped.url); - }, - - on_click_rename: function () { - var dialog = new CmisRenameContentDialog(this, this.document); - dialog.open(); - dialog.opened().then(function (result) { - dialog.$el.find('[autofocus]').focus(); - }); - }, - - on_click_import_new_version: function () { - var self = this; - this.cmis_session.checkOut(self.document.objectId) - .ok(function(data) { - var dialog = new CmisCheckinDialog(self, self.wrap_cmis_object(data), true); - dialog.onDestroy(function() { - self.verify_is_checked_out() - .then(function (isCheckedOut) { - if (isCheckedOut) { - return self._cancel_checkout.bind(self)(); - } - }) - .catch((error) => console.log("error")) - .finally(() => self.trigger_up("reload")); - }); - dialog.open(); - }) - .notOk(function() { - self._cancel_checkout.bind(self); - }); - - }, - - on_click_show_history: function () { - var self = this; - // without the objectId in the options, this returns a "versionSeriesId does not exist" error. - // Exactly why it does is anyone's guess... - var options = {includeAllowableActions: true, objectId: this.value}; - this.cmis_session.getAllVersions(this.value, options) - .ok(function (versions) { - var wrapped_versions = self.wrap_cmis_objects(versions); - var dialog = new CmisVersionsHistoryDialog(self, wrapped_versions); - dialog.open(); - }); - - }, - - on_click_toggle_details: function () { - var self = this; - var $documentDetails = this.$el.find(".document-details"); - this.$el.find('.toggle-document-details').toggleClass('fa-minus fa-plus-circle'); - if ($documentDetails.is(":empty")) { - $documentDetails.append(QWeb.render("cmisDocumentContentDetails", {object: this.document})); - this.$el.find('.toggle-document-details').on('click', function (e) { - e.preventDefault(); - e.stopPropagation(); - self.on_click_toggle_details(); - }); - } else { - $documentDetails.empty(); - } - }, - - on_click_delete_link: function () { - let self = this; - let values = {}; - values[this.name] = false; - this._rpc({ - model: this.model, - method: "write", - args: [this.res_id, values], - }).then(function() { - self.value = "empty"; - self._render(); - }); - }, - - on_cancel_checkout: function (e) { - var self = this; - this._cancel_checkout().finally(() => self.trigger_up('reload')); - }, - - _cancel_checkout: function () { - var self = this; - return new Promise(function(resolve, reject) { - self.cmis_session.cancelCheckOut(self.document.objectId) - .ok((data) => resolve(data)) - .notOk((error) => reject(error)); - }); - }, - - verify_is_checked_out: function() { - var self = this; - return new Promise(function (resolve) { - self.cmis_session.getObject(self.document.objectId) - .ok(function (doc) { - resolve(self.wrap_cmis_object(doc).isCheckedOut); - }) - .notOk(function (doc) { - // safer to assume the doc is checked out - resolve(true); - }); - }); - }, - - toggle_more_action: function name() { - var element = this.$el[0]; - if (!element || element.disabled || $(element).hasClass("disabled")) { - return; - } - var menu = this.$el.find('ul')[0]; - var isActive = $(element).hasClass("show"); - if (isActive) { - $(element).focus(); - element.setAttribute('aria-expanded', true); - }else{ - $(element).focus(); - element.setAttribute('aria-expanded', false); - - } - $(menu).toggleClass("show"); - $(element).toggleClass("show"); - }, - - register_document_action_events: function () { - var self = this; - var $el_actions = this.$el.find('.field_cmis_document_actions'); - var more_action = $el_actions.find('.cmis-dropdown-more-actions'); - more_action.on('click', function (e) { - e.stopPropagation() ; - self.toggle_more_action(); - }); - $el_actions.find('.content-action-preview').on('click', function (e) { - self.stopEvent(e); - self.on_click_preview(); - }); - $el_actions.find('.content-action-download').on('click', function (e) { - self.stopEvent(e); - self.on_click_download(); - }); - $el_actions.find('.content-action-rename').on('click', function (e) { - self.stopEvent(e); - self.on_click_rename(); - }); - $el_actions.find('.content-action-checkin').on('click', function (e) { - self.stopEvent(e); - self.on_click_import_new_version(); - }); - $el_actions.find('.content-action-history').on('click', function (e) { - self.stopEvent(e); - self.on_click_show_history(); - }); - $el_actions.find('.content-action-get-properties').on('click', function (e) { - self.stopEvent(e); - self.on_click_toggle_details(); - }); - $el_actions.find('.content-action-cancel-checkout').on('click', function (e) { - self.stopEvent(e); - self.on_cancel_checkout(); - }); - $el_actions.find('.content-action-delete-link').on('click', function(e) { - self.stopEvent(e); - self.on_click_delete_link(); - }) - }, - - register_no_document: function () { - var self = this; - this.$el.find('.content-action-add').on('click', function (e) { - self.stopEvent(e); - self.onClickAddDocument(); - }); - - this.$el.find('.content-action-link').on('click', function (e) { - self.stopEvent(e); - self.onClickLinkDocument(); - }); - }, - - stopEvent: function (e) { - e.preventDefault(); - e.stopPropagation(); - }, - }); - - var FieldCmisFolder = basicFields.FieldChar.extend(CmisMixin, { - template: "FieldCmisFolder", - - widget_class: 'field_cmis_folder', - datatable: null, - displayed_folder_id: null, - - events: { - 'change input': 'store_dom_value', - 'click td.details-control': 'on_click_details_control', - 'click button.cmis-create-root': 'on_click_create_root', - }, - - /* - * Override base methods - */ - - init: function () { - this._super.apply(this, arguments); - CmisMixin.init.call(this); - this.id_for_table = _.uniqueId('field_cmis_folder_widgets_table'); - this.table_rendered = $.Deferred(); - this.on('cmis_node_created', this, this.on_cmis_node_created); - this.on('cmis_node_deleted', this, this.on_cmis_node_deleted); - this.on('cmis_node_updated', this, this.on_cmis_node_updated); - this.on('cmis_node_content_updated', this, this.on_cmis_node_content_updated); - this.on('wrapped_cmis_node_reloaded', this, this.on_wrapped_cmis_node_reloaded); - this.backend = this.field.backend; - this.formatType = 'char'; - this.clipboardAction = undefined; - this.clipboardObject = undefined; - }, - - reset_widget: function () { - if (this.datatable) { - this.table_rendered = $.Deferred(); - this.datatable.destroy(); - this.datatable = null; - this.root_folder_id = null; - this.displayed_folder_id = null; - } - }, - - destroy: function () { - this.reset_widget(); - this._super.apply(this, arguments); - }, - - _render: function () { - this._super.apply(this, arguments); - this.states = []; - this.load_cmis_config(); - this.init_cmis_session(); - var value = this.value; - if (this.$input) { - this.$input.val(value); - } - if (!this.res_id) { - // hide the widget if the record is not yet created - this.$el.hide(); - } - this.$el.find('button.cmis-create-root').addClass('o_hidden'); - - if (this.$el.is(':visible')) { - // if the element is visible, we render it. If it's in a tab - // the rendition will be don on tab activation - this.render_datatable(); - } - - this.set_root_folder_id(value); - if (!value && this.field.allow_create) { - var self = this; - this.$el.find('button.cmis-create-root').removeClass('o_hidden'); - } - var self = this; - self.add_tab_listener(); - }, - - _renderReadonly: function () { - // in edit mode we need the in - this._prepareInput(this.$el); - }, - /** - * @override - */ - isSet: function () { - return true; - }, - - /* - * Cmis content events - */ - on_cmis_node_created: function (cmisobjects) { - this.refresh_datatable(); - }, - - on_cmis_node_deleted: function (cmisobjects) { - this.refresh_datatable(); - }, - - on_cmis_node_updated: function (cmisobjects) { - this.refresh_datatable(); - }, - - on_wrapped_cmis_node_reloaded: function (oldValue, newValue) { - this.refresh_datatable(); - }, - - on_cmis_node_content_updated: function (cmisobjects) { - this.on_cmis_node_updated(cmisobjects); - }, - - /* - * Specific methods - */ - - /** - * Create a node for the current model into the DMS - */ - on_click_create_root: function () { - if (!this.res_id) { - Dialog.alert(this, _t('Create your object first')); - return; - } - var self = this; - $.when(this.cmis_config_loaded).done(function () { - self._rpc({route:'/web/cmis/field/create_value', params:{ - 'model_name': self.model, - 'res_id': self.res_id, - 'field_name': self.name - }}).then(function (vals) { - var cmis_objectid = vals[self.res_id]; - var changes = {}; - changes[self.name] = cmis_objectid; - self.trigger_up('field_changed', { - dataPointID: self.dataPointID, - changes: changes, - }); - }); - }); - }, - - /** - * Add tab listener to render the table only when the tabe is active - * if the control is displayed in an inactive tab - */ - add_tab_listener: function () { - var self = this; - $(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) { - var tab_id = self.id_for_table; - var active_tab = $(e.target.hash); - if (active_tab.find('#' + tab_id).length == 1) { - self.render_datatable(); - return; - } - }); - }, - - get_datatable_config: function () { - var l10n = _t.database.parameters; - var self = this; - var config = { - searching: false, - scrollY: '40vh', - scrollCollapse: true, - pageLength: 25, - deferRender: true, - serverSide: true, - autoWidth: false, - responsive: true, - colReorder: { - realtime: false, - }, - stateSave: true, - ajax: $.proxy(self, 'datatable_query_cmis_data'), - buttons: [{ - extend: 'collection', - text: _t('Columns') + '', - buttons: ['columnsToggle'], - }], - columns: [ - { - className: 'details-control', - orderable: false, - data: 'fDetails()', - defaultContent: '', - width: '12px' - }, - {data: 'fName()'}, - { - data: 'title', - visible: false - }, - {data: 'description'}, - { - data: 'fLastModificationDate()', - width: '120px' - }, - { - data:'fCreationDate()', - width:'120px', - visible: false, - }, - { - data: 'lastModifiedBy', - width: '60px', - visible: false, - }, - { - data: 'fContentActions()', - defaultContent: '', - orderable: false, - width: "80px", - }, - ], - select: false, - rowId: 'objectId', - language: { - "decimal": l10n.decimal_point, - "emptyTable": _t("No data available in table"), - "info": _t("Showing _START_ to _END_ of _TOTAL_ entries"), - "infoEmpty": _t("Showing 0 to 0 of 0 entries"), - "infoFiltered": _t("(filtered from _MAX_ total entries)"), - "infoPostFix": _t(""), - "thousands": l10n.thousands_sep, - "lengthMenu": _t("Show _MENU_ entries"), - "loadingRecords": _t("Loading..."), - "processing": _t("Processing..."), - "search": _t("Search:"), - "zeroRecords": _t("No matching records found"), - "paginate": { - "first": _t("First"), - "last": _t("Last"), - "next": _t("Next"), - "previous": _t("Previous") - }, - "aria": { - "sortAscending": _t(": activate to sort column ascending"), - "sortDescending": _t(": activate to sort column descending") - } - }, - dom: "<'row'<'col-sm-6 cmis-root-content-buttons'><'col-sm-6'Blf>>" + - "<'row'<'col-sm-12'<'cmis-breadcrumb-container'>>>" + - "<'row'<'col-sm-12'tr>>" + - "<'row'<'col-sm-5'i><'col-sm-7'p>>", - "order": [[1, 'asc']] - }; - return config; - }, - - render_datatable: function () { - if (_.isNull(this.datatable)) { - var self = this; - this.$datatable = this.$el.find('#' + this.id_for_table); - this.$datatable.on('preInit.dt', $.proxy(self, 'on_datatable_preinit')); - this.$datatable.on('draw.dt', $.proxy(self, 'on_datatable_draw')); - this.$datatable.on('column-reorder.dt', $.proxy(self, 'on_datatable_column_reorder')); - var config = this.get_datatable_config(); - this.datatable = this.$datatable.DataTable(config); - this.table_rendered.resolve(); - } else { - this.datatable.draw(); - } - }, - - /** - * This method is called by DataTables when a table is being initialised - * and is about to request data. At the point of being called the table will - * have its columns and features initialised, but no data will have been - * loaded (either by Ajax, or reading from the DOM). - */ - on_datatable_preinit: function (e, settings) { - this.$breadcrumb = $('