Files
ERP/sophal/js/view/view.dms.js

1813 lines
47 KiB
JavaScript

/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
function inputDialog(options, callback) {
var opts = _.extend({
value: "",
title: "",
titleOK: _t("OK"),
titleCancel: _t("Cancel")
}, options);
var html = "" +
"<div>" +
"<input type='text'>" +
"</div>";
var dialog;
function close() {
if (dialog) {
dialog.dialog("close");
dialog = null;
}
}
dialog = axelor.dialogs.box(html, {
title: opts.title,
buttons: [{
'text': opts.titleCancel,
'class': 'btn',
'click': close
}, {
'text': opts.titleOK,
'class': 'btn btn-primary',
'click': submit
}],
onOpen: function(e) {
$(e.target).find('input').val(opts.value);
}
})
.on("keypress", "input", function (e) {
if (e.keyCode === 13) {
submit();
}
});
function submit() {
var value = dialog.find("input").val().trim();
if (value) {
return callback(value, close);
}
return close();
}
dialog.parent().addClass("dms-folder-dialog");
setTimeout(function() {
dialog.find("input").focus().select();
});
}
ui.controller("DMSFileListCtrl", DMSFileListCtrl);
DMSFileListCtrl.$inject = ['$scope', '$element', 'NavService'];
function DMSFileListCtrl($scope, $element, NavService) {
ui.GridViewCtrl.call(this, $scope, $element);
var _params = $scope._viewParams;
var _domain = $scope._domain || "";
if (_domain) {
_domain += " AND ";
}
$scope.$emptyMessage = _t("No documents found.");
$scope.$confirmMessage = function() {
var strong = function (text, quote) {
return "<strong>" + (quote ? "<em>\"" + text + "\"</em>" : text) + "</strong>";
};
var all = getSelectedAll();
if (all.length === 1 || $scope.currentFolder) {
var doc = _.first(all) || $scope.currentFolder;
return _t('Are you sure you want to delete {0}?', strong(doc.fileName));
}
return _t("Are you sure you want to delete the {0} selected documents?", strong(all.length));
};
$scope.currentFilter = null;
$scope.currentFolder = null;
$scope.currentPaths = [];
Object.defineProperty($scope, "_domain", {
get: function () {
if ($scope.currentFilter && $scope.getCurrentHome()) {
return _domain + "self.isDirectory = false AND self.parent.id = " + $scope.getCurrentHome().id;
}
if ($scope.currentFilter) {
return _domain + "self.isDirectory = false";
}
var parent = $scope.getCurrentParent();
if (parent && parent.id) {
return _domain + "self.parent.id = " + parent.id;
}
return _domain + "self.parent is null";
},
set: function () {
}
});
$scope.getCurrentHome = function () {
return _params.currentHome;
};
$scope.getCurrentParent = function () {
var base = $scope.currentFolder || $scope.getCurrentHome();
if (base && base.id > 0) {
return _.pick(base, "id");
}
return base;
};
$scope.addRelatedValues = function (record, baseFolder) {
// apply details about related object
var base = baseFolder || $scope.currentFolder;
if (!base || !base.relatedModel) {
base = $scope.getCurrentHome();
}
if (base) {
record.relatedId = base.relatedId;
record.relatedModel = base.relatedModel;
}
return record;
};
$scope.onEdit = function() {
var record = getSelected();
if (!record) {
var records = _.map($scope.selection, function(i) {
return $scope.dataView.getItem(i);
});
if (records.length) {
record = records[0];
} else {
record = $scope.currentFolder;
}
}
if (record) {
if ($scope._isPopup) {
$scope.onClose();
}
NavService.openTabByName("dms.file", {
mode: "edit",
state: record.id
});
}
};
$scope.sync = function () {
};
$scope.reload = function () {
var fields = _.pluck($scope.fields, 'name');
var ds = $scope._dataSource;
var context = $scope.getContext();
return ds.search({
fields: _.unique(fields),
domain: $scope._domain,
context: context
});
};
$scope._dataSource.on("change", function (e, records) {
$scope.$broadcast('on:accept-folders', records);
});
function resetFilter() {
$scope.currentFilter = null;
$scope._dataSource._filter = null;
$scope._dataSource._domain = null;
$scope._dataSource._page.from = 0;
$scope.$broadcast("on:clear-filter-silent");
}
var __onShow = $scope.onShow;
$scope.onShow = function (viewPromise) {
$scope.waitForActions(function () {
__onShow.call($scope, viewPromise);
});
};
var __filter = $scope.filter;
$scope.filter = function (searchFilter) {
var filter = _.extend({}, searchFilter);
var fields = $scope.fields || {};
_.each(["relatedId", "relatedModel", "isDirectory", "metaFile.id"], function (name) {
fields[name] = fields[name] || { name: name };
});
var advance = !_.isEmpty(filter.criteria) || !_.isEmpty(filter._domains);
if (advance) {
$scope.currentFilter = filter;
$scope.currentFolder = null;
$scope.currentPaths.length = 0;
} else {
resetFilter();
}
filter._domain = $scope._domain;
filter._context = $scope.getContext();
return __filter.call($scope, filter);
};
$scope.onFolder = function(folder, currentPaths) {
// reset filter
resetFilter();
var paths = currentPaths || $scope.currentPaths || [];
var index = paths.indexOf(folder);
if (index > -1) {
paths = paths.splice(0, index + 1);
}
if (folder && index === -1) {
paths.push(folder);
}
if (!folder) {
paths = [];
}
$scope.currentFolder = folder;
$scope.currentPaths = paths;
return $scope.reload();
};
$scope.onItemClick = function(event, args) {
var elem = $(event.target);
$scope.$timeout(function () {
var record = getSelected();
if (elem.is('.fa-file')) {
$scope.onEdit();
$scope.$applyAsync();
return;
}
if (elem.is('.fa-folder')) return $scope.onFolder(record);
if (elem.is('.fa-download')) return $scope.onDownload(record);
if (elem.is('.fa-info-circle')) return $scope.onDetails(record);
if (elem.is('.fa') && record) {
if (record.contentType === "html" || record.contentType === "spreadsheet") {
return $scope.onEditFile(record);
}
$scope.onEdit();
$scope.$applyAsync();
}
});
};
$scope.onItemDblClick = function(event, args) {
var elem = $(event.target);
if (elem.hasClass("fa")) return;
var record = getSelected();
if (record.typeIcon === "fa fa-folder") {
return $scope.onFolder(record);
}
setTimeout(function () {
if (record) {
if (record.contentType === "html" || record.contentType === "spreadsheet") {
return $scope.onEditFile(record);
}
}
$scope.onEdit();
$scope.$applyAsync();
});
};
function getSelected() {
var index = _.first($scope.selection || []);
return $scope.dataView.getItem(index);
}
function getSelectedAll() {
return _.map($scope.selection || [], function(i) { return $scope.dataView.getItem(i); });
}
function onNew(options, callback) {
if (!$scope.canCreateDocument(true)) {
return;
}
var opts = _.extend({
name: _t("New Folder"),
title: _t("Create folder")
}, options);
var count = 1;
var selected = $scope.getActiveFolder() || {};
var existing = _.pluck((selected.nodes || []), "fileName");
existing = existing.concat(_.pluck($scope.dataView.getItems(), "fileName"));
var name = opts.name;
while(existing.indexOf(name) > -1) {
name = opts.name + " (" + (++count) + ")";
}
inputDialog({
value: name,
title: opts.title,
titleOK: _t("Create")
}, function (value, done) {
var parent = $scope.getCurrentParent();
var record = _.extend({}, opts.record, {
fileName: value
});
if (parent && parent.id > 0) {
record.parent = parent;
}
record = $scope.addRelatedValues(record);
var promise = $scope._dataSource.save(record);
promise.then(done, done);
promise.success(function (record) {
$scope.reload();
callback(record);
});
});
}
$scope.onNewFolder = function () {
onNew({
name: _t("New Folder"),
title: _t("Create folder"),
record: {
isDirectory: true
}
}, function (record) {
$scope.$broadcast('on:accept-folders', [record]);
});
};
$scope.onNewDoc = function () {
onNew({
name: _t("New Document"),
title: _t("Create document"),
record: {
isDirectory: false,
contentType: "html"
}
}, function (record) {
$scope.onEditFile(record);
});
};
$scope.onNewSheet = function () {
onNew({
name: _t("New Spreadsheet"),
title: _t("Create spreadsheet"),
record: {
isDirectory: false,
contentType: "spreadsheet"
}
}, function (record) {
$scope.onEditFile(record);
});
};
$scope.onRename = function () {
var record = getSelected() || $scope.currentFolder;
if (!record || !record.id) {
return;
}
inputDialog({
value: record.fileName
}, function(value, done) {
if (record.fileName !== value) {
record.fileName = value;
rename(record, done);
} else {
done();
}
});
function rename(record, done) {
var rec = _.pick(record, ['id', 'version', 'fileName']);
var ds = $scope._dataSource;
if (record === $scope.currentFolder) {
ds = ds._new(ds._model);
}
var promise = ds.save(rec);
promise.then(done, done);
promise.success(function (res) {
record.version = res.version;
var folder = $scope.folders[record.id];
if (folder) {
folder.version = record.version;
folder.fileName = record.fileName;
}
if ($scope._dataSource === ds) {
$scope.reload();
}
});
}
};
var __onDelete = $scope.onDelete;
$scope.onDelete = function () {
var record = getSelected();
if (record) {
return __onDelete.apply($scope, arguments);
}
record = $scope.currentFolder;
if (!record || !record.id) {
return;
}
var message = $scope.$confirmMessage();
axelor.dialogs.confirm(message, function(confirmed) {
if (!confirmed) {
return;
}
var rec = _.pick(record, ['id', 'version']);
$scope._dataSource.removeAll([rec]).success(function() {
$scope.onFolder(record.parent);
});
});
};
$scope.onMoveFiles = function (files, toFolder) {
var items = files || [];
if (items.length === 0) {
return;
}
items = items.map(function (item) {
var res = _.extend({}, item);
if (toFolder && toFolder.id) {
res.parent = {
id: toFolder.id
};
} else {
res.parent = null;
}
// reset related record
res.relatedId = null;
res.relatedModel = null;
return res;
});
var folders = items.filter(function (item) { return item.isDirectory; });
$scope._dataSource.saveAll(items).success(function (records) {
$scope.$broadcast('on:change-folders', folders);
setTimeout(function () {
$scope.reload();
});
});
};
$scope.onDownload = function () {
var http = $scope._dataSource._http;
var records = _.map($scope.selection, function (i) { return $scope.dataView.getItem(i); });
if (records.length === 0 && $scope.currentFolder) {
records = [$scope.currentFolder];
}
var ids = _.pluck(_.compact(records), "id");
if (ids.length === 0) {
return;
}
http.post("ws/dms/download/batch", {
model: $scope._model,
records: ids
})
.then(function (res) {
var data = res.data;
var batchId = data.batchId;
var batchName = data.batchName;
if (batchId) {
ui.download("ws/dms/download/" + batchId, batchName);
}
});
};
$scope.onOffline = function () {
var http = $scope._dataSource._http;
var record = getSelected() || {};
if (record.id > 0) {
http.post("ws/dms/offline", {
model: $scope._model,
records: [record.id],
data: {
unset: !record.offline
}
}).then(function (res) {
record.offline = !record.offline;
});
}
};
$scope.onShowRelated = function () {
var record = getSelected() || {};
var id = record.relatedId;
var model = record.relatedModel;
if (id && model) {
$scope.$root.openTabByName("form::" + model, {
"mode": "edit",
"state": id
});
}
};
$scope.canShowRelated = function () {
var record = getSelected();
return record && !!record.relatedId;
};
$scope.onShowMembers = function () {
};
$scope.canCreateDocument = function (notify) {
var parent = $scope.currentFolder || $scope.getCurrentHome();
if (parent && parent.canWrite === false) {
if (notify) {
axelor.notify.error(_t("You can't create document here."));
}
return false;
}
return true;
};
$scope.canEditFile = function () {
var record = getSelected();
return record && !!record.contentType;
};
$scope.onEditFile = function (record) {
if ($scope._isPopup) {
$scope.onClose();
}
record = record || getSelected();
var view = {
action: "$act:dms" + record.id,
model: $scope._model,
title: record.contentType === "spreadsheet" ? _t("Spreadsheet") : _t("Document"),
viewType: "form",
views: [{
type: "form",
width: "large",
items: [{
type: "panel",
items: [{
type: "panel",
items: [{
type: "button",
title: _t("Save"),
icon: "fa-save",
onClick: "save",
colSpan: "3"
}, {
type: "button",
title: _t("Download"),
icon: "fa-download",
onClick: "save,action-dms-file-download",
colSpan: "3"
}]}, {
type: "field",
name: "content",
showTitle: false,
widget: record.contentType || "html",
colSpan: 12,
height: 520
}]
}]
}],
recordId: record.id,
forceEdit: true,
params: {
'show-toolbar': false
}
};
$scope.$root.openTab(view);
$scope.waitForActions(function () {
var formScope = view.$viewScope;
if (formScope) {
formScope.$on("$destroy", function () {
if (formScope.record) {
record.version = formScope.record.version;
}
});
}
});
};
}
ui.directive('uiDmsUploader', ['$q', '$http', function ($q, $http) {
return function (scope, element, attrs) {
var input = element.find("input.upload-input");
var dndTimer = null;
var dndInternal = false;
var dndDropClass = "dropping";
var dndDragClass = "dragging";
var dndEvents = "dragstart,dragend,dragover,dragenter,dragleave,drop".split(",");
function clearClassName(force) {
if (dndTimer) {
clearTimeout(dndTimer);
}
if (force) {
element.removeClass(dndDropClass);
return;
}
dndTimer = setTimeout(function () {
element.removeClass(dndDropClass);
}, 100);
}
function dragAndDropHandler(e) {
if (element.is(":hidden")) {
return;
}
switch (e.type) {
case "dragstart":
case "dragend":
dndInternal = e.type === "dragstart";
break;
case "dragover":
onDragOver(e);
break;
case "dragenter":
break;
case "dragleave":
clearClassName();
break;
case "drop":
clearClassName();
onDropFiles(e);
break;
}
}
function onDragOver(e) {
if (dndInternal) return;
clearClassName(true);
if (element.is(e.target) || element.has(e.target).length) {
element.addClass(dndDropClass);
} else {
clearClassName();
}
}
function onDropFiles(e) {
if (dndInternal) return;
if (element.is(e.target) || element.has(e.target)) {
doUpload(e.dataTransfer.files);
}
}
dndEvents.forEach(function (name) {
document.addEventListener(name, dragAndDropHandler);
});
scope.$on("$destroy", function() {
dndEvents.forEach(function (name) {
document.removeEventListener(name, dragAndDropHandler);
});
});
var uploads = {
items: [],
pending: [],
running: false,
queue: function (info) {
info.pending = true;
info.progress = 0;
info.transfer = _t("Pending");
info.abort = function () {
info.transfer = _t("Cancelled");
info.pending = false;
};
info.retry = function () {
uploads.queue(info);
uploads.process();
};
if (this.items.indexOf(info) === -1) {
this.items.push(info);
}
if (this.pending.indexOf(info) === -1) {
this.pending.push(info);
}
},
process: function () {
if (this.running || this.pending.length === 0) {
if (_.all(this.items, function (item) { return item.complete; })) {
this.items.length = 0;
}
return;
}
this.running = true;
var info = this.pending.shift();
while (info && !info.pending) {
info = this.pending.shift();
}
if (!info) {
this.running = false;
return;
}
var that = this;
var promise = uploadSingle(info);
function error(reason) {
that.running = false;
info.active = false;
info.pending = false;
info.progress = 0;
info.transfer = reason.message;
info.failed = true;
return that.process();
}
function success() {
that.running = false;
info.active = false;
info.pending = false;
info.complete = true;
info.progress = "100%";
return that.process();
}
return promise.then(success, error);
}
};
// expose uploads
scope.uploads = uploads;
scope.onCloseUploadFiles = function() {
uploads.items.length = 0;
uploads.pending.length = 0;
};
var uploadSize = +(axelor.config["file.upload.size"]) || 0;
function doUpload(files) {
if (!scope.canCreateDocument(true)) {
return;
}
var all = files;
if (files.fileName) {
files = [files];
}
var i, file;
for (i = 0; i < all.length; i++) {
file = all[i];
if (uploadSize > 0 && file.size > 1048576 * uploadSize) {
return axelor.dialogs.say(_t("You are not allow to upload a file bigger than") + ' ' + uploadSize + 'MB');
}
}
for (i = 0; i < all.length; i++) {
file = all[i];
var info = {
file: file
};
uploads.queue(info);
}
uploads.process();
}
function uploadSingle(info) {
var deferred = $q.defer();
var promise = deferred.promise;
var file = info.file;
var xhr = new XMLHttpRequest();
function formatSize(done, total) {
function format(size) {
if(size > 1000000000) return parseFloat(size/1000000000).toFixed(2) + " GB";
if(size > 1000000) return parseFloat(size/1000000).toFixed(2) + " MB";
if(size >= 1000) return parseFloat(size/1000).toFixed(2) + " KB";
return size + " B";
}
return format(done || 0) + "/" + format(total);
}
function doClean() {
return $http({
method: "DELETE",
url: "ws/files/upload/" + info.uuid,
silent: true,
transformRequest: []
});
}
function onError(reason) {
function done() {
var message = reason && reason.error ? reason.error : _t("Failed");
deferred.reject({ message: message, failed: true });
}
doClean().then(done, done);
}
function onCancel(clean) {
function done() {
deferred.reject({ message: _t("Cancelled"), cancelled: true });
}
return clean ? doClean().then(done, done) : done();
}
function onSuccess(meta) {
var ds = scope._dataSource;
var parent = scope.getCurrentParent();
var record = {
fileName: meta.fileName,
metaFile: _.pick(meta, "id")
};
if (parent && parent.id > 0) {
record.parent = parent;
}
record = scope.addRelatedValues(record);
ds.save(record).success(function (dmsFile) {
deferred.resolve(info);
}).error(onError);
}
function onChunk(response) {
info._start = info._end;
info._end = Math.min(info._end + info._size, file.size);
if (response && response.fileId) {
info.uuid = response.fileId;
}
if (response && response.id) {
return onSuccess(response);
}
if (info.loaded) {
return onError();
}
// continue with next chunk
sendChunk();
scope.$applyAsync();
}
function sendChunk() {
xhr.open("POST", "ws/files/upload", true);
xhr.overrideMimeType("application/octet-stream");
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (info.uuid) {
xhr.setRequestHeader("X-File-Id", info.uuid);
}
xhr.setRequestHeader("X-File-Name", encodeURIComponent(file.name));
xhr.setRequestHeader("X-File-Type", file.type);
xhr.setRequestHeader("X-File-Size", file.size);
xhr.setRequestHeader("X-File-Offset", info._start);
if (info._end > file.size) {
info._end = file.size;
}
var chunk = file.slice(info._start, info._end);
xhr.send(chunk);
}
info.uuid = null;
info._start = 0;
info._size = 1000 * 1000; // 1MB
info._end = info._size;
info.active = true;
info.transfer = formatSize(0, file.size);
info.abort = function () {
xhr.abort();
onCancel();
};
info.retry = function () {
// put back on queue
uploads.queue(info);
uploads.process();
};
xhr.upload.addEventListener("progress", function (e) {
var total = info._start + e.loaded;
var done = Math.round((total / file.size) * 100);
info.progress = done > 95 ? "95%" : done + "%";
info.transfer = formatSize(total, file.size);
info.loaded = total === file.size;
scope.$applyAsync();
});
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4) {
switch(xhr.status) {
case 0:
case 406:
onCancel(true);
break;
case 200:
onChunk(xhr.responseText ? angular.fromJson(xhr.responseText) : null);
break;
default:
onError(xhr.responseText ? angular.fromJson(xhr.responseText) : null);
}
scope.$applyAsync();
}
};
sendChunk();
return promise;
}
input.change(function() {
scope.$applyAsync(function () {
doUpload(input.get(0).files);
input.val(null);
});
});
scope.onUpload = function () {
input.click();
};
};
}]);
DmsFolderTreeCtrl.$inject = ["$scope", "DataSource"];
function DmsFolderTreeCtrl($scope, DataSource) {
var ds = DataSource.create("com.axelor.dms.db.DMSFile");
$scope.folders = {};
$scope.rootFolders = [];
function syncFolders(records) {
var home = $scope.getCurrentHome();
var folders = {};
var rootFolders = [];
_.each(records, function (item) {
folders[item.id] = item;
item.nodes = [];
});
_.each(records, function (item) {
var parent = folders[item["parent.id"]];
if (parent) {
parent.nodes.push(item);
item.parent = parent;
}
});
home = home || {
open: true,
active: true,
fileName: _t("Home")
};
home.open = true;
home.nodes = _.filter(records, function (item) {
return !item.parent;
});
rootFolders = [home];
// sync with old state
_.each($scope.folders, function (item, id) {
var folder = folders[id];
if (folder) {
folder.open = item.open;
folder.active = item.active;
}
});
_.each($scope.rootFolders, function (root, i) {
var folder = rootFolders[i];
if (folder) {
folder.open = root.open;
folder.active = root.active;
}
});
$scope.folders = folders;
$scope.rootFolders = rootFolders;
}
$scope.getActiveFolder = function () {
for (var id in $scope.folders) {
var folder = $scope.folders[id];
if (folder && folder.active) {
return folder;
}
}
var home = _.first($scope.rootFolders);
if (home && home.active) {
return home;
}
};
function getDomain() {
var params = $scope._viewParams;
var domain = params.domain || "";
var home = $scope.getCurrentHome();
if (domain) {
domain = domain + " AND ";
}
domain += "self.isDirectory = true";
if (home && home.id > 0) {
domain += " AND self.id != " + home.id + " AND self.parent.id = " + home.id;
} else {
domain += " AND self.parent is NULL";
}
if (home && home.relatedModel) {
domain += " AND self.relatedModel = '" + home.relatedModel + "'";
}
if (home && home.relatedId) {
domain += " AND self.relatedId = " + home.relatedId;
}
return domain;
}
$scope.sync = function () {
return ds.search({
fields: ["fileName", "parent.id"],
domain: getDomain(),
context: { _populate: false },
limit: -1
}).success(syncFolders);
};
$scope.showTree = !axelor.device.small;
$scope.onToggleTree = function () {
$scope.showTree = !$scope.showTree;
axelor.$adjustSize();
};
$scope.onFolderClick = function (node) {
if (!node || !node.id || node.home) {
$scope.onFolder();
$scope.$applyAsync();
return;
}
var paths = [];
var parent = node.parent;
while (parent) {
paths.unshift(parent);
parent = parent.parent;
}
$scope.onFolder(node, paths);
$scope.$applyAsync();
};
}
ui.directive("uiDmsFolders", function () {
return {
controller: DmsFolderTreeCtrl,
link: function (scope, element, attrs) {
scope.onGridInit = _.once(function (grid, instance) {
grid.onDragInit.subscribe(function (e, dd) {
e.stopImmediatePropagation();
});
grid.onDragStart.subscribe(function (e, dd) {
var cell = grid.getCellFromEvent(e);
if (!cell) return;
dd.row = cell.row;
var record = grid.getDataItem(dd.row);
if (!record || !record.id) {
return;
}
e.stopImmediatePropagation();
dd.mode = "recycle";
var rows = grid.getSelectedRows();
if (rows.length === 0 || rows.indexOf(dd.row) === -1) {
rows = [dd.row];
}
grid.setSelectedRows(rows);
grid.setActiveCell(cell.row, cell.cell);
dd.rows = rows;
dd.count = rows.length;
dd.records = _.map(rows, function (i) {
return grid.getDataItem(i);
});
var text = "<span>" + record.fileName + "</span>";
if (dd.count > 1) {
text += " <span class='badge badge-important'>"+ dd.count +"</span>";
}
var proxy = $("<div class='grid-dnd-proxy'></div>")
.hide()
.html(text)
.appendTo("body");
dd.helper = proxy;
return proxy;
});
grid.onDrag.subscribe(function (e, dd) {
if (dd.mode != "recycle") { return; }
dd.helper.show().css({top: e.pageY + 5, left: e.pageX + 5});
});
grid.onDragEnd.subscribe(function (e, dd) {
if (dd.mode != "recycle") { return; }
dd.helper.remove();
});
$.drop({
mode: "intersect"
});
});
scope.$watch("currentFolder", function dmsCurrentFolderWatch(folder) {
var folders = scope.folders || {};
var rootFolders = scope.rootFolders || [];
var id, node;
for (id in folders) {
folders[id].active = false;
}
(rootFolders[0]||{}).active = false;
id = (folder||{}).id;
node = folders[id] || rootFolders[0];
if (node) {
node.active = true;
}
});
function syncHome(record) {
var home = scope.getCurrentHome();
if (home && home.id > 0) return;
if (home && record && record.parent) {
home.id = record.parent.id;
}
}
scope._dataSource.on("on:save", function (e) {
syncHome(scope._dataSource.at(0));
});
scope._dataSource.on("on:remove", function (e, records) {
var ids = _.pluck(records, 'id');
records
.filter(function (record) { return record.id in scope.folders; })
.forEach(function (record) {
var found = scope.folders[record.id];
var parent = scope.folders[(found.parent||{}).id] || _.first(scope.rootFolders);
if (parent) {
parent.nodes = _.filter(parent.nodes, function (node) {
return ids.indexOf(node.id) === -1;
});
}
delete scope.folders[record.id];
});
});
scope.$on('on:accept-folders', function (e, records) {
var parent = scope.getActiveFolder() || _.first(scope.rootFolders);
var folders = records.filter(function (record) {
return record.isDirectory && !scope.folders[record.id];
});
folders.forEach(function (folder) {
folder.nodes = [];
folder.parent = parent;
scope.folders[folder.id] = folder;
parent.nodes.push(folder);
});
});
scope.$on('on:change-folders', function (e, records) {
var folders = records.filter(function (record) { return record.isDirectory; });
// first remove from old parent
folders.forEach(function (record) {
var found = scope.folders[record.id];
var parent = scope.folders[((found||{}).parent||{}).id] || _.first(scope.rootFolders);
if (parent) {
parent.nodes = _.filter(parent.nodes, function (node) {
return node.id !== record.id;
});
}
});
// and add to new parent
folders.forEach(function (record) {
var found = scope.folders[record.id] || (scope.folders[record.id] = record);
var oldParent = found.parent;
var newParent = scope.folders[(record.parent||{}).id] || _.first(scope.rootFolders);
if (found && oldParent !== newParent) {
newParent.nodes.push(found);
found.parent = newParent;
}
found.nodes = found.nodes || [];
});
});
scope.sync();
},
template: "<ul ui-dms-tree x-handler='this' x-nodes='rootFolders' class='dms-tree'></ul>"
};
});
ui.directive("uiDmsTreeNode", function () {
return {
scope: true,
link: function (scope, element, attrs) {
element.children('.highlight').on("xdropstart", function (e, dd) {
var records = dd.records;
if (!records || records.length === 0) {
return;
}
if (scope.node.active) {
return;
}
for (var i = 0; i < records.length; i++) {
var record = records[i];
var current = scope.node;
while(current) {
if (record.id === current.id) { return; }
if (record.parent && record.parent.id === current.id) { return; }
current = current.parent;
}
}
element.addClass("dropping");
});
element.children('.highlight').on("xdropend", function (e, dd) {
element.removeClass("dropping");
});
element.children('.highlight').on("xdrop", function (e, dd) {
var records = dd.records;
if (!records || records.length === 0 || !scope.node) {
return;
}
if (!element.hasClass("dropping")) {
return;
}
scope.onMoveFiles(records, scope.node);
});
},
replace: true,
template: "" +
"<a href='' ng-click='onClick($event, node)' ng-class='{active: node.active}'>" +
"<span class='highlight'></span>" +
"<i class='fa fa-caret-down handle' ng-show='node.open'></i> " +
"<i class='fa fa-caret-right handle' ng-show='!node.open'></i> " +
"<i class='fa fa-folder'></i> " +
"<span class='title'>{{node.fileName}}</span>" +
"</a>"
};
});
ui.directive("uiDmsTree", ['$compile', function ($compile) {
var template = "" +
"<li ng-repeat='node in nodes track by node.id' ng-class='{empty: !node.nodes.length}' class='dms-tree-folder'>" +
"<a x-node='node' ui-dms-tree-node></a>" +
"<ul ng-if='node.open' x-handler='handler' x-nodes='node.nodes' ui-dms-tree></ul>" +
"</li>";
return {
scope: {
nodes: "=",
handler: "="
},
link: function (scope, element, attrs) {
var handler = scope.handler;
scope.onClick = function (e, node) {
if ($(e.target).is("i.handle")) {
node.open = !node.open;
return;
}
return handler.onFolderClick(node);
};
scope.onMoveFiles = function (files, toFolder) {
return handler.onMoveFiles(files, toFolder);
};
$compile(template)(scope).appendTo(element);
}
};
}]);
ui.directive("uiDmsDetails", function () {
return {
controller: ["$scope", function ($scope) {
function getUserName(record) {
if(!record) {
return null;
}
var key = axelor.config["user.nameField"] || "name";
return record[key] || record.name || record.code;
}
function set(record) {
var info = $scope.details = {};
if (record) {
info.id = record.id;
info.version = record.version;
info.name = record.fileName;
info.type = record.isDirectory ? _t("Directory") : record.fileType || _t("Unknown");
info.tags = record.tags;
info.owner = getUserName(record.createdBy);
info.created = moment(record.createdOn).format('DD/MM/YYYY HH:mm');
info.updated = moment(record.updatedOn || record.createdOn).format('DD/MM/YYYY HH:mm');
info.canOffline = !record.isDirectory && (record.metaFile || record['metaFile.id']);
info.offline = record.offline;
}
}
$scope.tagsFormName = "dms-file-tags-form";
$scope.showDetails = false;
$scope.showTagEditor = false;
$scope.onDetails = function (record) {
$scope.showDetails = true;
axelor.$adjustSize();
set(record);
};
$scope.onCloseDetails = function () {
$scope.showDetails = false;
$scope.showTagEditor = false;
axelor.$adjustSize();
};
$scope.onAddTags = function () {
$scope.showTagEditor = true;
};
$scope.onSaveTags = function () {
var ds = $scope._dataSource;
function doClose(rec) {
$scope.showTagEditor = false;
$scope.details.tags = rec.tags;
$scope.details.version = rec.version;
}
var record = _.pick($scope.details, "id", "version", "tags");
ds.save(record).success(doClose);
};
$scope.$watch("selection[0]", function dmsSelectionWatch(index) {
if (index === undefined || !$scope.showDetails) return;
var details = $scope.details || {};
var record = $scope.dataView.getItem(index) || {};
if (details.id !== record.id) {
$scope.onCloseDetails();
}
});
}],
link: function (scope, element, attrs) {
//XXX: ui-dialog issue
element.zIndex(element.siblings(".slickgrid").zIndex() + 2);
}
};
});
// members popup
ui.directive("uiDmsMembersPopup", ["$compile", function ($compile) {
return {
controller: ["$scope", function($scope) {
$scope.onSavePermissions = function () {
$scope._onSavePermissions();
};
}],
link: function (scope, element, attrs) {
var form = null;
scope.permissionFormName = "dms-file-permission-form";
Object.defineProperty(scope, 'permissionFormTitle', {
get: function () {
return scope.fileName ? _t("Permissions ({0})", scope.fileName) : _t("Permissions");
}
});
function getSelected() {
var index = _.first(scope.selection || []);
return scope.dataView.getItem(index) || scope.currentFolder;
}
scope.canShare = function () {
var record = getSelected();
return record && record.canShare;
};
scope.onPermissions = function () {
if (form === null) {
form = $("<div ui-dms-inline-form></div>")
.attr("x-record", "record")
.attr("x-form-name", "permissionFormName")
.attr("x-form-title", "permissionFormTitle");
form = $compile(form)(scope);
form.appendTo(element);
form.width("100%");
}
var record = getSelected();
var formScope = form.isolateScope();
scope.fileName = record.fileName;
formScope.doRead(record.id).success(function (rec) {
formScope.edit(rec);
formScope.setEditable(true);
setTimeout(function () {
element.dialog("option", "height", 320);
element.dialog("option", "title", scope.permissionFormTitle);
element.dialog("open");
});
});
};
scope._onSavePermissions = function () {
var ds = scope._dataSource._new("com.axelor.dms.db.DMSPermission");
var formScope = form.isolateScope();
if (!formScope.isValid()) {
return axelor.notify.error(_t("Invalid permissions"));
}
var record = formScope.record;
var original = formScope.$$original.permissions || [];
var toSave = _.map(record.permissions, function (item) {
item.file = _.pick(record, "id");
return item;
});
var toRemove = _.filter(original, function (item) {
return !_.findWhere(toSave, { id: item.id });
});
function doClose() {
element.dialog("close");
formScope.edit(null);
scope.fileName = null;
}
var promise = null;
if (toRemove.length) {
promise = ds._request('removeAll').post({
records: toRemove
});
}
if (toSave.length) {
promise = promise ? promise.then(function () {
return ds.saveAll(toSave);
}) : ds.saveAll(toSave);
}
if (promise) {
promise.then(doClose);
} else {
doClose();
}
};
scope.$on("$destroy", function () {
if (form) {
var formScope = form.isolateScope();
if (formScope) {
formScope.$destroy();
}
form = null;
}
});
},
replace: true,
template:
"<div ui-dialog x-on-ok='onSavePermissions' x-css='ui-dialog-small dms-permission-popup' title='Permissions'></div>"
};
}]);
ui.directive("uiDmsInlineForm", function () {
return {
scope: {
formName: "=",
formTitle: "=",
record: "="
},
controller: ["$scope", "$element", 'DataSource', 'ViewService', function($scope, $element, DataSource, ViewService) {
$scope._viewParams = {
action: _.uniqueId('$act'),
title: $scope.formTitle,
model: "com.axelor.dms.db.DMSFile",
viewType: "form",
views: [{
type: "form",
name: $scope.formName
}]
};
ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
ui.FormViewCtrl.call(this, $scope, $element);
$scope.setEditable();
$scope.onHotKey = function (e) {
e.preventDefault();
return false;
};
}],
link: function (scope, element, attrs) {
},
template: "<div ui-view-form x-handler='true'></div>"
};
});
// attachment popup
ui.directive("uiDmsPopup", ['$compile', function ($compile) {
return {
scope: {
onSelect: "&"
},
controller: ["$scope", 'DataSource', 'ViewService', 'NavService', function($scope, DataSource, ViewService, NavService) {
$scope._isPopup = true;
$scope._viewParams = {
action: _.uniqueId('$act'),
model: "com.axelor.dms.db.DMSFile",
viewType: "grid",
views: [{
type: "grid",
name: "dms-file-grid"
}]
};
ui.ViewCtrl.apply(this, arguments);
var ds = DataSource.create("com.axelor.dms.db.DMSFile");
$scope.findHome = function (forScope, success) {
var home = {};
var record = forScope.record;
function objectName() {
return _.humanize(_.last(forScope._model.split(".")));
}
function findName() {
for (var name in forScope.fields) {
if (forScope.fields[name].nameColumn) {
return record[name];
}
}
return record.name || _.lpad(record.id, 5, '0');
}
var domain = "" +
"self.isDirectory = true AND " +
"self.relatedId = :rid AND " +
"self.relatedModel = :rmodel AND " +
"self.parent.relatedModel = :rmodel AND " +
"(self.parent.relatedId is null OR self.parent.relatedId = 0)";
var context = {
"rid": record.id,
"rmodel": forScope._model
};
function process(home) {
if (!home) {
home = {};
home.id = -1;
home.fileName = findName();
home.relatedModel = forScope._model;
home.relatedId = record.id;
}
home.home = true;
home.open = true;
home.active = true;
return home;
}
ds.search({
limit: 1,
fields: ['fileName', 'relatedModel', 'relatedId'],
domain: domain,
context: context
}).success(function (records) {
success(process(_.first(records)));
});
};
$scope.countAttachments = function (forScope, done) {
var ds = DataSource.create("com.axelor.dms.db.DMSFile");
var record = forScope.record;
var domain = "self.relatedModel = :name AND self.relatedId = :id " +
"AND COALESCE(self.isDirectory, FALSE) = FALSE";
var context = {name: forScope._model, id: record.id };
var promise = ds.search({
fields: ['id'],
domain: domain,
context: context
});
promise.success(function (records) {
record.$attachments = _.size(records);
forScope.$broadcast('on:load-messages', record);
});
return promise.then(done, done);
};
$scope.onClose = function () {
$scope._onClose();
};
if ($scope.onSelect()) {
$scope.buttons = [{
text: _t("Select"),
'class': 'btn btn-primary',
click: function (e) {
var viewScope = $(this).find(".grid-view").scope();
var items = _.map(viewScope.selection, function (i) {
return viewScope.dataView.getItem(i);
});
$scope._onSelectFiles(items);
}
}];
}
}],
link: function (scope, element, attrs) {
scope._onSelectFiles = function (items) {
scope.$applyAsync(function () {
var promise = scope.onSelect()(items);
if (promise && promise.then) {
promise.then(function () {
element.dialog("close");
});
} else {
element.dialog("close");
}
});
};
scope.$evalAsync(function () {
scope._setTitle(_t('Attachments'));
});
scope.onHotKey = function (e, action) {
var elem = element.find(".grid-view:first");
var viewScope = elem.scope();
if (viewScope && viewScope.onHotKey) {
return viewScope.onHotKey(e, action);
}
};
var formScope = null;
scope._onClose = function () {
if (formScope) {
scope.countAttachments(formScope, function () {
scope.$destroy();
});
} else {
scope.$destroy();
}
formScope = null;
};
scope.showPopup = function (forScope) {
formScope = forScope;
function doOpen() {
var content = "<div ng-include='\"partials/views/dms-file-list.html\"'></div>";
content = $compile(content)(scope);
content.appendTo(element);
scope._doShow();
setTimeout(function () {
//XXX: ui-dialog issue
element.find('.filter-box').zIndex(element.zIndex() + 1);
}, 100);
}
if (!formScope) {
return doOpen();
}
scope.findHome(forScope, function (home) {
scope._viewParams.currentHome = home;
doOpen();
});
};
},
replace: true,
template: "<div ui-dialog ui-dialog-size x-buttons='buttons' x-on-ok='false' x-on-close='onClose' class='dms-popup' title='Attachments'></div>"
};
}]);
ui.download = function download(url, fileName) {
function doDownload() {
var link = document.createElement('a');
var name = axelor.sanitize(fileName);
link.innerHTML = name;
link.download = name;
link.href = url;
_.extend(link.style, {
position: "absolute",
visibility: "hidden",
zIndex: 1000000000
});
document.body.appendChild(link);
link.onclick = function(e) {
setTimeout(function () {
document.body.removeChild(e.target);
}, 300);
};
setTimeout(function () {
link.click();
}, 100);
axelor.notify.info(_t("Downloading {0}...", name));
}
$.ajax({
url : url,
type : 'HEAD',
success : doDownload,
error : function (e) {
if (e.status == 404) {
var fname = "<strong>" + name + "</strong>";
axelor.notify.error("<p>" + _t("File {0} does not exist.", fname) + "</p>");
}
}
});
};
// prevent download on droping files
$(function () {
window.addEventListener("dragover",function(e) {
e.preventDefault();
}, false);
window.addEventListener("drop",function(e) {
e.preventDefault();
}, false);
});
})();