From b6036de8e2ba8ffb08736524998111357850e5fa Mon Sep 17 00:00:00 2001 From: nathangray Date: Mon, 21 Dec 2020 13:53:24 -0700 Subject: [PATCH] * Etemplate: Add context menu action on linked files to copy them somewhere else Also, update vfsSelect to use request(), so it processes the additional stuff in response so we can have a success message. --- api/js/etemplate/et2_widget_link.js | 47 +++++++++++++++++------- api/js/etemplate/et2_widget_link.ts | 44 +++++++++++++++++++---- api/js/etemplate/et2_widget_vfs.js | 4 +-- api/js/etemplate/et2_widget_vfs.ts | 8 ++--- api/js/jsapi/egw_global.d.ts | 56 +++++++++++++++++++++++++++++ api/src/Etemplate/Widget/Link.php | 17 +++++++++ 6 files changed, 152 insertions(+), 24 deletions(-) diff --git a/api/js/etemplate/et2_widget_link.js b/api/js/etemplate/et2_widget_link.js index 06046af788..b4d59107df 100644 --- a/api/js/etemplate/et2_widget_link.js +++ b/api/js/etemplate/et2_widget_link.js @@ -38,6 +38,7 @@ var et2_core_inheritance_1 = require("./et2_core_inheritance"); var et2_core_valueWidget_1 = require("./et2_core_valueWidget"); var et2_core_inputWidget_1 = require("./et2_core_inputWidget"); var et2_widget_selectbox_1 = require("./et2_widget_selectbox"); +var et2_widget_dialog_1 = require("./et2_widget_dialog"); /** * UI widgets for Egroupware linking system */ @@ -131,7 +132,7 @@ var et2_link_to = /** @class */ (function (_super) { select: function () { self.link_button.show(); return true; }, readonly: this.options.readonly }; - this.link_entry = et2_createWidget("link-entry", link_entry_attrs, this); + this.link_entry = et2_core_widget_1.et2_createWidget("link-entry", link_entry_attrs, this); // Filemanager select var select_attrs = { button_label: egw.lang('Link'), @@ -169,7 +170,7 @@ var et2_link_to = /** @class */ (function (_super) { select_attrs.method = 'EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_existing'; select_attrs.method_id = self.options.value.to_app + ':' + self.options.value.to_id; } - this.vfs_select = et2_createWidget("vfs-select", select_attrs, this); + this.vfs_select = et2_core_widget_1.et2_createWidget("vfs-select", select_attrs, this); this.vfs_select.set_readonly(this.options.readonly); // File upload var file_attrs = { @@ -198,7 +199,7 @@ var et2_link_to = /** @class */ (function (_super) { self.createLink(event); } }; - this.file_upload = et2_createWidget("file", file_attrs, this); + this.file_upload = et2_core_widget_1.et2_createWidget("file", file_attrs, this); this.file_upload.set_readonly(this.options.readonly); return true; }; @@ -1479,8 +1480,8 @@ var et2_link_list = /** @class */ (function (_super) { _this.context = new egwMenu(); _this.context.addItem("comment", _this.egw().lang("Comment"), "", function () { var link_id = typeof self.context.data.link_id == 'number' ? self.context.data.link_id : self.context.data.link_id.replace(/[:\.]/g, '_'); - et2_dialog.show_prompt(function (button, comment) { - if (button != et2_dialog.OK_BUTTON) + et2_widget_dialog_1.et2_dialog.show_prompt(function (button, comment) { + if (button != et2_widget_dialog_1.et2_dialog.OK_BUTTON) return; var remark = jQuery('#link_' + (self.context.data.dom_id ? self.context.data.dom_id : link_id), self.list).children('.remark'); if (isNaN(self.context.data.link_id)) // new entry, not yet stored @@ -1561,11 +1562,33 @@ var et2_link_list = /** @class */ (function (_super) { id: self.value.to_id }); }); + // Only allow this option if the entry has been saved, and has a real ID + if (self.options.value && self.options.value.to_id && typeof self.options.value.to_id != 'object') { + _this.context.addItem("copy_to", _this.egw().lang("Copy to"), _this.egw().image('copy'), function (menu_item) { + // Highlight files for nice UI indicating what will be copied + jQuery('[id="link_' + self.context.data.link_id + ']', this.list).effect('highlight', {}, 2000); + // Get target + var select_attrs = { + mode: "select-dir", + button_caption: '', + button_icon: 'copy', + button_label: egw.lang("copy"), + //extra_buttons: [{text: egw.lang("link"), id:"link", image: "link"}], + dialog_title: egw.lang('Copy to'), + method: "EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_copy_to", + method_id: self.context.data + }; + var vfs_select = et2_core_widget_1.et2_createWidget("vfs-select", select_attrs, self); + // No button, just open it + vfs_select.button.hide(); + vfs_select.click(null); + }); + } _this.context.addItem("-", "-"); _this.context.addItem("delete", _this.egw().lang("Delete link"), _this.egw().image("delete"), function (menu_item) { var link_id = isNaN(self.context.data.link_id) ? self.context.data : self.context.data.link_id; var row = jQuery('#link_' + (self.context.data.dom_id ? self.context.data.dom_id : self.context.data.link_id), self.list); - et2_dialog.show_dialog(function (button) { if (button == et2_dialog.YES_BUTTON) + et2_widget_dialog_1.et2_dialog.show_dialog(function (button) { if (button == et2_widget_dialog_1.et2_dialog.YES_BUTTON) self._delete_link(link_id, row); }, egw.lang('Delete link?')); }); // Native DnD - Doesn't play nice with jQueryUI Sortable @@ -1637,12 +1660,12 @@ var et2_link_list = /** @class */ (function (_super) { .appendTo(row) .addClass("icon"); if (_link_data.icon) { - var icon_widget = et2_createWidget("image", {}); + var icon_widget = et2_core_widget_1.et2_createWidget("image", {}); var src = ''; // Creat a mime widget if the link has type if (_link_data.type) { // VFS - file - var vfs_widget = et2_createWidget('vfs-mime', {}); + var vfs_widget = et2_core_widget_1.et2_createWidget('vfs-mime', {}); vfs_widget.set_value({ download_url: _link_data.download_url, name: _link_data.title, @@ -1717,8 +1740,8 @@ var et2_link_list = /** @class */ (function (_super) { // We don't use ui-icon because it assigns a bg image .addClass("delete icon") .bind('click', function () { - et2_dialog.show_dialog(function (button) { - if (button == et2_dialog.YES_BUTTON) { + et2_widget_dialog_1.et2_dialog.show_dialog(function (button) { + if (button == et2_widget_dialog_1.et2_dialog.YES_BUTTON) { self._delete_link(self.value && typeof self.value.to_id != 'object' && _link_data.link_id ? _link_data.link_id : _link_data, row); } }, egw.lang('Delete link?')); @@ -1923,13 +1946,13 @@ var et2_link_add = /** @class */ (function (_super) { // Already done return false; } - this.app_select = et2_createWidget("link-apps", jQuery.extend({}, this.options, { + this.app_select = et2_core_widget_1.et2_createWidget("link-apps", jQuery.extend({}, this.options, { 'id': this.options.id + 'app', value: this.options.application ? this.options.application : this.options.value && this.options.value.add_app ? this.options.value.add_app : null, application_list: this.options.application ? this.options.application : null }), this); this.div.append(this.app_select.getDOMNode()); - this.button = et2_createWidget("button", { id: this.options.id + "_add", label: this.egw().lang("add") }, this); + this.button = et2_core_widget_1.et2_createWidget("button", { id: this.options.id + "_add", label: this.egw().lang("add") }, this); this.button.set_label(this.egw().lang("add")); var self = this; this.button.click = function () { diff --git a/api/js/etemplate/et2_widget_link.ts b/api/js/etemplate/et2_widget_link.ts index a036027836..effa9e5e3a 100644 --- a/api/js/etemplate/et2_widget_link.ts +++ b/api/js/etemplate/et2_widget_link.ts @@ -14,17 +14,22 @@ /vendor/bower-asset/jquery-ui/jquery-ui.js; et2_core_inputWidget; et2_core_valueWidget; + et2_widget_selectbox; + expose; // Include menu system for list context menu egw_action.egw_menu_dhtmlx; */ -import {et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; +import {et2_createWidget, et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; import {ClassWithAttributes} from "./et2_core_inheritance"; import {et2_valueWidget} from "./et2_core_valueWidget"; import {et2_inputWidget} from "./et2_core_inputWidget"; import {et2_selectbox} from "./et2_widget_selectbox"; import {et2_button} from "./et2_widget_button"; +import {et2_dialog} from "./et2_widget_dialog"; +import {et2_file} from "./et2_widget_file"; +import {et2_vfsSelect} from "./et2_widget_vfs"; import {et2_vfs_select} from "./et2_widget_vfs"; /** @@ -594,11 +599,11 @@ export class et2_link_entry extends et2_inputWidget private cache: any = {}; - private div: JQuery; - private app_select: JQuery; - private search: JQuery; - private clear: JQuery; - private link_button: JQuery; + protected div: JQuery; + protected app_select: JQuery; + protected search: JQuery; + protected clear: JQuery; + protected link_button: JQuery; private response: any; private request: any; private last_search: string; @@ -1923,6 +1928,33 @@ export class et2_link_list extends et2_link_string id: self.value.to_id }); }); + + // Only allow this option if the entry has been saved, and has a real ID + if (self.options.value && self.options.value.to_id && typeof self.options.value.to_id != 'object') + { + this.context.addItem("copy_to", this.egw().lang("Copy to"), this.egw().image('copy'), function (menu_item) + { + // Highlight files for nice UI indicating what will be copied + jQuery('[id="link_' + self.context.data.link_id+']', this.list).effect('highlight', {}, 2000); + + // Get target + var select_attrs: any = { + mode: "select-dir", + button_caption: '', + button_icon: 'copy', + button_label: egw.lang("copy"), + //extra_buttons: [{text: egw.lang("link"), id:"link", image: "link"}], + dialog_title: egw.lang('Copy to'), + method: "EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_copy_to", + method_id: self.context.data + }; + let vfs_select = et2_createWidget("vfs-select", select_attrs, self); + + // No button, just open it + vfs_select.button.hide(); + vfs_select.click(null); + }); + } this.context.addItem("-", "-"); this.context.addItem("delete", this.egw().lang("Delete link"), this.egw().image("delete"), function(menu_item) { diff --git a/api/js/etemplate/et2_widget_vfs.js b/api/js/etemplate/et2_widget_vfs.js index 998753de98..5679b31f83 100644 --- a/api/js/etemplate/et2_widget_vfs.js +++ b/api/js/etemplate/et2_widget_vfs.js @@ -1156,9 +1156,9 @@ var et2_vfsSelect = /** @class */ (function (_super) { et2_vfsSelect._setRecentPaths(submit_value.path); self.value = files; if (self.options.method && self.options.method !== 'download') { - egw(window).json(self.options.method, [self.options.method_id, files, submit_button_id, savemode], function () { + egw(window).request(self.options.method, [self.options.method_id, files, submit_button_id, savemode]).then(function (data) { jQuery(self.node).change(); - }).sendRequest(true); + }); } else { jQuery(self.node).change(); diff --git a/api/js/etemplate/et2_widget_vfs.ts b/api/js/etemplate/et2_widget_vfs.ts index 6cea46ddaf..cd000c1970 100644 --- a/api/js/etemplate/et2_widget_vfs.ts +++ b/api/js/etemplate/et2_widget_vfs.ts @@ -1413,13 +1413,13 @@ export class et2_vfsSelect extends et2_inputWidget self.value = files; if (self.options.method && self.options.method !== 'download') { - egw(window).json( + egw(window).request( self.options.method, - [self.options.method_id, files, submit_button_id, savemode], - function(){ + [self.options.method_id, files, submit_button_id, savemode] + ).then(function(data){ jQuery(self.node).change(); } - ).sendRequest(true); + ); } else { diff --git a/api/js/jsapi/egw_global.d.ts b/api/js/jsapi/egw_global.d.ts index 1f028f8c1e..d9e1fa8e90 100644 --- a/api/js/jsapi/egw_global.d.ts +++ b/api/js/jsapi/egw_global.d.ts @@ -736,6 +736,62 @@ declare interface IegwWndLocal extends IegwGlobal * @param _sender is a parameter being passed to the _callback function */ json(_menuaction : string, _parameters? : any[], _callback? : Function, _context? : object, _async? : boolean|"keepalive", _sender?) : JsonRequest; + + /** + * Do an AJAX call and get a javascript promise, which will be resolved with the returned data. + * + * egw.request() returns immediately with a Promise. The promise will be resolved with just the returned data, + * any other "piggybacked" responses will be handled by registered handlers. The data will also be passed to + * any registered data handlers (egw.data) before it is passed to your handler. + * + * To use: + * @example + * egw.request( + * "EGroupware\\Api\\Etemplate\\Widget\\Select::ajax_get_options", + * ["select-cat"] + * ) + * .then(function(data) { + * // Deal with the returned data here. data may be undefined if no data was returned. + * console.log("Here's the categories:",data); + * }); + * + * + * The return is a Promise, so multiple .then() can be chained in the usual ways: + * @example + * egw.request(...) + * .then(function(data) { + * if(debug) console.log("Requested data", data); + * } + * .then(function(data) { + * // Change the data for the rest of the chain + * if(typeof data === "undefined") return []; + * } + * .then(function(data) { + * // data is never undefined now, if it was before it's an empty array now + * for(let i = 0; i < data.length; i++) + * { + * ... + * } + * } + * + * + * You can also fire off multiple requests, and wait for them to all be answered: + * @example + * let first = egw.request(...); + * let second = egw.request(...); + * Promise.all([first, second]) + * .then(function(values) { + * console.log("First:", values[0], "Second:", values[1]); + * } + * + * + * @param {string} _menuaction + * @param {any[]} _parameters + * + * @return Promise + */ + request(_menuaction: string, param2: any[]): Promise; + /** * Registers a new handler plugin. * diff --git a/api/src/Etemplate/Widget/Link.php b/api/src/Etemplate/Widget/Link.php index 097081177f..001d79ff3c 100644 --- a/api/src/Etemplate/Widget/Link.php +++ b/api/src/Etemplate/Widget/Link.php @@ -286,6 +286,23 @@ class Link extends Etemplate\Widget } } + /** + * Copy a linked file to somewhere else in the VFS + * + * @param $file Array of file information + * @param $target Target path + * @param $button_id + */ + public static function ajax_copy_to($file_info, $target, $button_id) + { + $error_count = 0; + $copied = array(); + Api\Vfs::copy_files([Api\Link::vfs_path($file_info['app2'], $file_info['id2'], $file_info['id'], true)], $target,$error_count,$copied); + + $response = Api\Json\Response::get(); + $response->message(lang('%1 files copied',count($copied))); + } + public static function ajax_delete($value) { $response = Api\Json\Response::get();