diff --git a/api/js/etemplate/et2_widget_image.js b/api/js/etemplate/et2_widget_image.js
index 4370c1a6d9..8dbf4bd513 100644
--- a/api/js/etemplate/et2_widget_image.js
+++ b/api/js/etemplate/et2_widget_image.js
@@ -1,3 +1,4 @@
+"use strict";
/**
* EGroupware eTemplate2 - JS Description object
*
@@ -9,699 +10,602 @@
* @copyright Nathan Gray 2011
* @version $Id$
*/
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
/*egw:uses
- /vendor/bower-asset/jquery/dist/jquery.js;
- et2_core_interfaces;
- et2_core_baseWidget;
- expose;
- /vendor/bower-asset/cropper/dist/cropper.min.js;
+ /vendor/bower-asset/jquery/dist/jquery.js;
+ et2_core_interfaces;
+ et2_core_baseWidget;
+ expose;
+ /vendor/bower-asset/cropper/dist/cropper.min.js;
*/
-
+var et2_core_baseWidget_1 = require("./et2_core_baseWidget");
+var et2_core_widget_1 = require("./et2_core_widget");
+var et2_core_inheritance_1 = require("./et2_core_inheritance");
/**
* Class which implements the "image" XET-Tag
*
* @augments et2_baseWidget
*/
-var et2_image = (function(){ "use strict"; return expose(et2_baseWidget.extend([et2_IDetachedDOM],
-{
- attributes: {
- "src": {
- "name": "Image",
- "type": "string",
- "description": "Displayed image"
- },
- default_src: {
- name: "Default image",
- type: "string",
- description: "Image to use if src is not found"
- },
- "href": {
- "name": "Link Target",
- "type": "string",
- "description": "Link URL, empty if you don't wan't to display a link.",
- "default": et2_no_init
- },
- "extra_link_target": {
- "name": "Link target",
- "type": "string",
- "default": "_self",
- "description": "Link target descriptor"
- },
- "extra_link_popup": {
- "name": "Popup",
- "type": "string",
- "description": "widthxheight, if popup should be used, eg. 640x480"
- },
- "imagemap":{
- // TODO: Do something with this
- "name": "Image map",
- "description": "Currently not implemented"
- },
- "label": {
- "name": "Label",
- "type": "string",
- "description": "Label for image"
- },
- "expose_view":{
- name: "Expose view",
- type: "boolean",
- default: false,
- description: "Clicking on an image with href value would popup an expose view, and will show image referenced by href."
- }
- },
- legacyOptions: ["href", "extra_link_target", "imagemap", "extra_link_popup", "id"],
-
- /**
- * Constructor
- *
- * @memberOf et2_image
- */
- init: function() {
- this._super.apply(this, arguments);
-
- // Create the image or a/image tag
- this.image = jQuery(document.createElement("img"));
- if (this.options.label)
- {
- this.image.attr("alt", this.options.label).attr("title", this.options.label);
- }
- if (this.options.href)
- {
- this.image.addClass('et2_clickable');
- }
- if(this.options["class"])
- {
- this.image.addClass(this.options["class"]);
- }
- this.setDOMNode(this.image[0]);
- },
-
- click: function()
- {
- if(this.options.href)
- {
- this.egw().open_link(this.options.href, this.options.extra_link_target, this.options.extra_link_popup);
- }
- else
- {
- this._super.apply(this,arguments);
- }
- },
-
- transformAttributes: function(_attrs) {
- this._super.apply(arguments);
-
- // Check to expand name
- if (typeof _attrs["src"] != "undefined")
- {
- var manager = this.getArrayMgr("content");
- if(manager) {
- var src = manager.getEntry(_attrs["src"]);
- if (typeof src != "undefined" && src !== null)
- {
- if(typeof src == "object")
- {
- src = egw().link('/index.php', src);
- }
- _attrs["src"] = src;
- }
- }
- }
- },
-
- set_label: function(_value) {
- this.options.label = _value;
- _value = this.egw().lang(_value);
- // label is NOT the alt attribute in eTemplate, but the title/tooltip
- this.image.attr("alt", _value).attr("title", _value);
- },
-
- setValue: function(_value) {
- // Value is src, images don't get IDs
- this.set_src(_value);
- },
-
- set_href: function (_value)
- {
- if (!this.isInTree())
- {
- return false;
- }
-
- this.options.href = _value;
- this.image.wrapAll('"');
-
- var href = this.options.href;
- var popup = this.options.extra_link_popup;
- var target = this.options.extra_link_target;
- var self = this;
- this.image.click(function(e)
- {
- if (self.options.expose_view)
- {
- self._init_blueimp_gallery(e,_value);
- e.stopImmediatePropagation();
- }
- else
- {
- egw.open_link(href,target,popup);
- }
-
- e.preventDefault();
- return false;
- });
-
- return true;
- },
-
- /**
- * Set image src
- *
- * @param {string} _value image, app/image or url
- * @return {boolean} true if image was found, false if not (image is either not displayed or default_src is used)
- */
- set_src: function(_value) {
- if(!this.isInTree())
- {
- return false;
- }
-
- this.options.src = _value;
-
- // allow url's too
- if (_value[0] == '/' || _value.substr(0,4) == 'http' || _value.substr(0,5) == 'data:')
- {
- this.image.attr('src', _value).show();
- return true;
- }
- var src = this.egw().image(_value);
- if (src)
- {
- this.image.attr("src", src).show();
- return true;
- }
- src = null;
- if (this.options.default_src)
- {
- src = this.egw().image(this.options.default_src);
- }
- if (src)
- {
- this.image.attr("src", src).show();
- }
- else
- {
- this.image.css("display","none");
- }
- return false;
- },
-
- /**
- * Function to get media content to feed the expose
- * @param {type} _value
- * @returns {Array|Array.getMedia.mediaContent}
- */
- getMedia: function (_value)
- {
- var base_url = egw.webserverUrl.match(/^\//,'ig')?egw(window).window.location.origin + egw.webserverUrl + '/':egw.webserverUrl + '/';
- var mediaContent = [];
- if (_value)
- {
- mediaContent = [{
- title: this.options.label,
- href: base_url + _value,
- type: this.options.type + "/*",
- thumbnail: base_url + _value
- }];
- }
- return mediaContent;
- },
-
- /**
- * Implementation of "et2_IDetachedDOM" for fast viewing in gridview
- *
- * @param {array} _attrs
- */
- getDetachedAttributes: function(_attrs) {
- _attrs.push("src", "label", "href");
- },
-
- getDetachedNodes: function() {
- return [this.image[0]];
- },
-
- setDetachedAttributes: function(_nodes, _values) {
- // Set the given DOM-Nodes
- this.image = jQuery(_nodes[0]);
-
- // Set the attributes
- if (_values["src"])
- {
- this.set_src(_values["src"]);
- }
- // Not valid, but we'll deal
- if (_values["value"])
- {
- this.setValue(_values["value"]);
- }
-
- if (_values["label"])
- {
- this.set_label(_values["label"]);
- }
- if(_values["href"])
- {
- this.image.addClass('et2_clickable');
- this.set_href(_values["href"]);
- }
- }
-}));}).call(this);
-
-et2_register_widget(et2_image, ["image"]);
-
+var et2_image = /** @class */ (function (_super) {
+ __extends(et2_image, _super);
+ /**
+ * Constructor
+ */
+ function et2_image(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_image._attributes, _child || {})) || this;
+ _this.legacyOptions = ["href", "extra_link_target", "imagemap", "extra_link_popup", "id"];
+ _this.image = null;
+ // Create the image or a/image tag
+ _this.image = jQuery(document.createElement("img"));
+ if (_this.options.label) {
+ _this.image.attr("alt", _this.options.label).attr("title", _this.options.label);
+ }
+ if (_this.options.href) {
+ _this.image.addClass('et2_clickable');
+ }
+ if (_this.options["class"]) {
+ _this.image.addClass(_this.options["class"]);
+ }
+ _this.setDOMNode(_this.image[0]);
+ return _this;
+ }
+ et2_image.prototype.click = function (_ev) {
+ if (this.options.href) {
+ this.egw().open_link(this.options.href, this.options.extra_link_target, this.options.extra_link_popup);
+ }
+ else {
+ _super.prototype.click.call(this, _ev);
+ }
+ };
+ et2_image.prototype.transformAttributes = function (_attrs) {
+ _super.prototype.transformAttributes.call(this, _attrs);
+ // Check to expand name
+ if (typeof _attrs["src"] != "undefined") {
+ var manager = this.getArrayMgr("content");
+ if (manager) {
+ var src = manager.getEntry(_attrs["src"]);
+ if (typeof src != "undefined" && src !== null) {
+ if (typeof src == "object") {
+ src = egw().link('/index.php', src);
+ }
+ _attrs["src"] = src;
+ }
+ }
+ }
+ };
+ et2_image.prototype.set_label = function (_value) {
+ this.options.label = _value;
+ _value = this.egw().lang(_value);
+ // label is NOT the alt attribute in eTemplate, but the title/tooltip
+ this.image.attr("alt", _value).attr("title", _value);
+ };
+ et2_image.prototype.setValue = function (_value) {
+ // Value is src, images don't get IDs
+ this.set_src(_value);
+ };
+ et2_image.prototype.set_href = function (_value) {
+ if (!this.isInTree()) {
+ return false;
+ }
+ this.options.href = _value;
+ this.image.wrapAll('"');
+ var href = this.options.href;
+ var popup = this.options.extra_link_popup;
+ var target = this.options.extra_link_target;
+ var self = this;
+ this.image.click(function (e) {
+ if (self.options.expose_view) {
+ /*
+ TODO: Fix after implementing EXPOSE mixin class
+ */
+ //self._init_blueimp_gallery(e,_value);
+ e.stopImmediatePropagation();
+ }
+ else {
+ egw.open_link(href, target, popup);
+ }
+ e.preventDefault();
+ return false;
+ });
+ return true;
+ };
+ /**
+ * Set image src
+ *
+ * @param {string} _value image, app/image or url
+ * @return {boolean} true if image was found, false if not (image is either not displayed or default_src is used)
+ */
+ et2_image.prototype.set_src = function (_value) {
+ if (!this.isInTree()) {
+ return false;
+ }
+ this.options.src = _value;
+ // allow url's too
+ if (_value[0] == '/' || _value.substr(0, 4) == 'http' || _value.substr(0, 5) == 'data:') {
+ this.image.attr('src', _value).show();
+ return true;
+ }
+ var src = this.egw().image(_value);
+ if (src) {
+ this.image.attr("src", src).show();
+ return true;
+ }
+ src = null;
+ if (this.options.default_src) {
+ src = this.egw().image(this.options.default_src);
+ }
+ if (src) {
+ this.image.attr("src", src).show();
+ }
+ else {
+ this.image.css("display", "none");
+ }
+ return false;
+ };
+ /**
+ * Function to get media content to feed the expose
+ * @param {type} _value
+ */
+ et2_image.prototype.getMedia = function (_value) {
+ var base_url = egw.webserverUrl.match(/^\/ig/) ? egw(window).window.location.origin + egw.webserverUrl + '/' : egw.webserverUrl + '/';
+ var mediaContent = [];
+ if (_value) {
+ mediaContent = [{
+ title: this.options.label,
+ href: base_url + _value,
+ type: this.options.type + "/*",
+ thumbnail: base_url + _value
+ }];
+ }
+ return mediaContent;
+ };
+ /**
+ * Implementation of "et2_IDetachedDOM" for fast viewing in gridview
+ *
+ * @param {array} _attrs
+ */
+ et2_image.prototype.getDetachedAttributes = function (_attrs) {
+ _attrs.push("src", "label", "href");
+ };
+ et2_image.prototype.getDetachedNodes = function () {
+ return [this.image[0]];
+ };
+ et2_image.prototype.setDetachedAttributes = function (_nodes, _values) {
+ // Set the given DOM-Nodes
+ this.image = jQuery(_nodes[0]);
+ // Set the attributes
+ if (_values["src"]) {
+ this.set_src(_values["src"]);
+ }
+ // Not valid, but we'll deal
+ if (_values["value"]) {
+ this.setValue(_values["value"]);
+ }
+ if (_values["label"]) {
+ this.set_label(_values["label"]);
+ }
+ if (_values["href"]) {
+ this.image.addClass('et2_clickable');
+ this.set_href(_values["href"]);
+ }
+ };
+ et2_image._attributes = {
+ "src": {
+ "name": "Image",
+ "type": "string",
+ "description": "Displayed image"
+ },
+ default_src: {
+ name: "Default image",
+ type: "string",
+ description: "Image to use if src is not found"
+ },
+ "href": {
+ "name": "Link Target",
+ "type": "string",
+ "description": "Link URL, empty if you don't wan't to display a link.",
+ "default": et2_no_init
+ },
+ "extra_link_target": {
+ "name": "Link target",
+ "type": "string",
+ "default": "_self",
+ "description": "Link target descriptor"
+ },
+ "extra_link_popup": {
+ "name": "Popup",
+ "type": "string",
+ "description": "widthxheight, if popup should be used, eg. 640x480"
+ },
+ "imagemap": {
+ // TODO: Do something with this
+ "name": "Image map",
+ "description": "Currently not implemented"
+ },
+ "label": {
+ "name": "Label",
+ "type": "string",
+ "description": "Label for image"
+ },
+ "expose_view": {
+ name: "Expose view",
+ type: "boolean",
+ default: false,
+ description: "Clicking on an image with href value would popup an expose view, and will show image referenced by href."
+ }
+ };
+ return et2_image;
+}(et2_core_baseWidget_1.et2_baseWidget));
+et2_core_widget_1.et2_register_widget(et2_image, ["image"]);
/**
- * Widget displaying an application icon
- */
-var et2_appicon = (function(){ "use strict"; return et2_image.extend(
-{
- attributes: {
- default_src: {
- name: "Default image",
- type: "string",
- default: "nonav",
- description: "Image to use if there is no application icon"
- }
- },
-
- set_src: function(_app)
- {
- if (!_app) _app = this.egw().app_name();
- this.image.addClass('et2_appicon');
- this._super.call(this, _app == 'sitemgr-link' ? 'sitemgr/sitemgr-link' : // got removed from jdots
- (this.egw().app(_app, 'icon_app') || _app)+'/'+(this.egw().app(_app, 'icon') || 'navbar'));
- }
-});}).call(this);
-et2_register_widget(et2_appicon, ["appicon"]);
-
+* Widget displaying an application icon
+*/
+var et2_appicon = /** @class */ (function (_super) {
+ __extends(et2_appicon, _super);
+ function et2_appicon() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ et2_appicon.prototype.set_src = function (_app) {
+ if (!_app)
+ _app = this.egw().app_name();
+ this.image.addClass('et2_appicon');
+ return _super.prototype.set_src.call(this, _app == 'sitemgr-link' ? 'sitemgr/sitemgr-link' : // got removed from jdots
+ (this.egw().app(_app, 'icon_app') || _app) + '/' + (this.egw().app(_app, 'icon') || 'navbar'));
+ };
+ et2_appicon._attributes = {
+ default_src: {
+ name: "Default image",
+ type: "string",
+ default: "nonav",
+ description: "Image to use if there is no application icon"
+ }
+ };
+ return et2_appicon;
+}(et2_image));
+et2_core_widget_1.et2_register_widget(et2_appicon, ["appicon"]);
/**
- * Avatar widget to display user profile picture or
- * user letter avatar based on user's firstname lastname.
- *
- * @augments et2_baseWidget
- */
-
-var et2_avatar = (function(){ "use strict"; return et2_image.extend(
-{
- attributes: {
- "contact_id": {
- name: "Contact id",
- type: "string",
- default: "",
- description: "Contact id should be either user account_id {account:number} or contact_id {contact:number or number}"
- },
- "default_src": {
- "ignore": true
- },
- "frame": {
- name: "Avatar frame",
- type: "string",
- default: "circle",
- description: "Define the shape of frame that avatar will be shown inside it. it can get {circle,rectangle} values which default value is cicle."
- },
- editable: {
- name: "Edit avatar",
- type: "boolean",
- default: false,
- description: "Make avatar widget editable to be able to crop profile picture or upload a new photo"
- },
- crop: {
- name: "Crop avatar",
- type: "boolean",
- default: false,
- description: "Create crop container and cropping feature"
- }
- },
-
- init: function ()
- {
- this._super.apply(this,arguments);
- if (this.options.frame == 'circle')
- {
- this.image.attr('style', 'border-radius:50%');
- }
- if (this.options.contact_id) this.setValue(this.options.contact_id);
- },
-
- /**
- * Function to set contact id
- * contact id could be in one of these formats:
- * 'number', will be consider as contact_id
- * 'contact:number', similar to above
- * 'account:number', will be consider as account id
- * @example: contact_id = "account:4"
- *
- * @param {string} _contact_id contact id could be as above mentioned formats
- */
- set_contact_id: function(_contact_id)
- {
- var params = {};
- var id = 'contact_id';
-
- this.image.addClass('et2_avatar');
-
- if (!_contact_id)
- {
- _contact_id = this.egw().user('account_id');
- }
- else if(_contact_id.match(/account\:/))
- {
- id = 'account_id';
- _contact_id = _contact_id.replace('account:','');
- }
- else
- {
- id = 'contact_id';
- _contact_id = _contact_id.replace('contact:', '');
- }
-
- // if our src (incl. cache-buster) already includes the correct id, use that one
- if (this.options.src && this.options.src.match("(&|\\?)contact_id="+_contact_id+"(&|\\$)"))
- {
- return;
- }
-
- // we have only the id, so we need to bypass caching with a cache-buster
- params[id] = _contact_id;
- params._cache = (new Date).getTime();
-
- var url = egw.link('/api/avatar.php',params);
- this.set_src(url);
- },
-
- /**
- * Function to set value
- * @param {string} _value
- */
- setValue: function(_value)
- {
- this.set_contact_id(_value);
- },
-
- /**
- * Implementation of "et2_IDetachedDOM" for fast viewing in gridview
- *
- * @param {array} _attrs
- */
- getDetachedAttributes: function(_attrs) {
- _attrs.push("contact_id", "label", "href");
- },
-
- setDetachedAttributes: function (_nodes, _values)
- {
- // Set the given DOM-Nodes
- this.image = jQuery(_nodes[0]);
-
- if (_values["contact_id"])
- {
- this.set_contact_id(_values["contact_id"]);
- }
-
- if (_values["label"])
- {
- this.set_label(_values["label"]);
- }
- if(_values["href"])
- {
- this.image.addClass('et2_clickable');
- this.set_href(_values["href"]);
- }
- },
-
- /**
- * Build Editable Mask Layer (EML) in order to show edit/delete actions
- * on top of profile picture.
- * @param {boolean} _noDelete disable delete button in initialization
- */
- _buildEditableLayer: function (_noDelete)
- {
- var self = this;
- // editable mask layer (eml)
- var eml = jQuery(document.createElement('div'))
- .addClass('eml')
- .insertAfter(this.image);
-
- // edit button
- var edit = jQuery(document.createElement('div'))
- .addClass('emlEdit')
- .click(function(){
- var buttons = [
- {"button_id": 1,"text": self.egw().lang('save'), id: 'save', image: 'check', "default":true},
- {"button_id": 0,"text": self.egw().lang('cancel'), id: 'cancel', image: 'cancelled'}
- ];
- var dialog = function(_title, _value, _buttons, _egw_or_appname)
- {
- return et2_createWidget("dialog",
- {
- callback: function(_buttons, _value)
- {
- if (_buttons == 'save')
- {
- var canvas = jQuery('#_cropper_image').cropper('getCroppedCanvas');
- self.image.attr('src', canvas.toDataURL("image/jpeg", 1.0));
- self.egw().json('addressbook.addressbook_ui.ajax_update_photo',
- [self.getInstanceManager().etemplate_exec_id, canvas.toDataURL('image/jpeg',1.0)],
- function(res)
- {
- if (res)
- {
- del.show();
- }
- }).sendRequest();
- }
- },
- title: _title||egw.lang('Input required'),
- buttons: _buttons||et2_dialog.BUTTONS_OK_CANCEL,
- value: {
- content: _value
- },
- width: "90%",
- height:"450",
- resizable: false,
- position:"top+10",
- template: egw.webserverUrl+'/api/templates/default/avatar_edit.xet?2'
- }, et2_dialog._create_parent(_egw_or_appname));
- };
-
- dialog(egw.lang('Edit avatar'),{photo:self.options.contact_id},buttons);
- })
- .appendTo(eml);
-
- // delete button
- var del = jQuery(document.createElement('div'))
- .addClass('emlDelete')
- .click(function(){
- et2_dialog.show_dialog(function(_btn){
- if (_btn == et2_dialog.YES_BUTTON)
- {
- self.egw().json('addressbook.addressbook_ui.ajax_update_photo',
- [self.getInstanceManager().etemplate_exec_id, null],
- function(res)
- {
- if (res)
- {
- self.image.attr('src','');
- del.hide();
- egw.refresh('Avatar Deleted.', egw.app_name());
- }
- }).sendRequest();
- }
- }, egw.lang('Delete this photo?'), egw.lang('Delete'), null, et2_dialog.BUTTONS_YES_NO);
- })
- .appendTo(eml);
- if (_noDelete) del.hide();
- // invisible the mask
- eml.css('opacity','0');
-
- eml.parent().css('position', "relative");
-
- // bind handler for activating actions on editable mask
- eml.on({
- mouseover:function(){eml.css('opacity','0.9');},
- mouseout: function (){eml.css('opacity','0');}
- });
- },
-
- /**
- * We need to build the Editable Mask Layer after widget gets loaded
- */
- doLoadingFinished: function ()
- {
- this._super.apply(this,arguments);
- var self = this;
- if (this.options.contact_id && this.options.editable)
- {
- egw(window).json(
- 'addressbook.addressbook_ui.ajax_noPhotoExists',
- [this.options.contact_id],
- function(noPhotoExists)
- {
- if (noPhotoExists) self.image.attr('src','');
- self._buildEditableLayer(noPhotoExists);
- }
- ).sendRequest(true);
- }
- if (this.options.crop)
- {
- var cropped = jQuery(this.image).cropper({
- aspectRatio: 1/1,
- crop: function (e){
- console.log (e);
- }
- });
-
- }
- }
-
-});}).call(this);
-et2_register_widget(et2_avatar, ["avatar"]);
-
+* Avatar widget to display user profile picture or
+* user letter avatar based on user's firstname lastname.
+*
+* @augments et2_baseWidget
+*/
+var et2_avatar = /** @class */ (function (_super) {
+ __extends(et2_avatar, _super);
+ function et2_avatar(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_avatar._attributes, _child || {})) || this;
+ if (_this.options.frame == 'circle') {
+ _this.image.attr('style', 'border-radius:50%');
+ }
+ if (_this.options.contact_id)
+ _this.setValue(_this.options.contact_id);
+ return _this;
+ }
+ /**
+ * Generate letter avatar with given data
+ * @param {type} _fname
+ * @param {type} _lname
+ * @param {type} _id
+ * @returns {string} return data url
+ */
+ et2_avatar.lavatar = function (_fname, _lname, _id) {
+ var str = _fname + _lname + _id;
+ var getBgColor = function (_str) {
+ var hash = 0;
+ for (var i = 0; i < _str.length; i++) {
+ hash = _str[i].charCodeAt(0) + hash;
+ }
+ return et2_avatar.LAVATAR_BG_COLORS[hash % et2_avatar.LAVATAR_BG_COLORS.length];
+ };
+ var bg = getBgColor(str);
+ var size = et2_avatar.LAVATAR_SIZE * (window.devicePixelRatio ? window.devicePixelRatio : 1);
+ var text = (_fname ? _fname[0].toUpperCase() : "") + (_lname ? _lname[0].toUpperCase() : "");
+ var canvas = document.createElement('canvas');
+ canvas.width = size;
+ canvas.height = size;
+ var context = canvas.getContext("2d");
+ context.fillStyle = bg;
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ context.font = Math.round(canvas.width / 2) + "px Arial";
+ context.textAlign = "center";
+ context.fillStyle = et2_avatar.LAVATAR_TEXT_COLOR;
+ context.fillText(text, size / 2, size / 1.5);
+ var dataURL = canvas.toDataURL();
+ canvas.remove();
+ return dataURL;
+ };
+ /**
+ * Function runs after uplaod in avatar dialog is finished and it tries to
+ * update image and cropper container.
+ * @param {type} e
+ */
+ et2_avatar.uploadAvatar_onFinish = function (e) {
+ var file = e.data.resumable.files[0].file;
+ var reader = new FileReader();
+ reader.onload = function (e) {
+ jQuery('#_cropper_image').attr('src', e.target.result);
+ jQuery('#_cropper_image').cropper('replace', e.target.result);
+ };
+ reader.readAsDataURL(file);
+ };
+ /**
+ * Function to set contact id
+ * contact id could be in one of these formats:
+ * 'number', will be consider as contact_id
+ * 'contact:number', similar to above
+ * 'account:number', will be consider as account id
+ * @example: contact_id = "account:4"
+ *
+ * @param {string} _contact_id contact id could be as above mentioned formats
+ */
+ et2_avatar.prototype.set_contact_id = function (_contact_id) {
+ var params = {};
+ var id = 'contact_id';
+ this.image.addClass('et2_avatar');
+ if (!_contact_id) {
+ _contact_id = this.egw().user('account_id');
+ }
+ else if (_contact_id.match(/account:/)) {
+ id = 'account_id';
+ _contact_id = _contact_id.replace('account:', '');
+ }
+ else {
+ id = 'contact_id';
+ _contact_id = _contact_id.replace('contact:', '');
+ }
+ // if our src (incl. cache-buster) already includes the correct id, use that one
+ if (this.options.src && this.options.src.match("(&|\\?)contact_id=" + _contact_id + "(&|\\$)")) {
+ return;
+ }
+ params[id] = _contact_id;
+ this.set_src(egw.link('/api/avatar.php', params));
+ };
+ /**
+ * Function to set value
+ */
+ et2_avatar.prototype.setValue = function (_value) {
+ this.set_contact_id(_value);
+ };
+ /**
+ * Implementation of "et2_IDetachedDOM" for fast viewing in gridview
+ */
+ et2_avatar.prototype.getDetachedAttributes = function (_attrs) {
+ _attrs.push("contact_id", "label", "href");
+ };
+ et2_avatar.prototype.setDetachedAttributes = function (_nodes, _values) {
+ // Set the given DOM-Nodes
+ this.image = jQuery(_nodes[0]);
+ if (_values["contact_id"]) {
+ this.set_contact_id(_values["contact_id"]);
+ }
+ if (_values["label"]) {
+ this.set_label(_values["label"]);
+ }
+ if (_values["href"]) {
+ this.image.addClass('et2_clickable');
+ this.set_href(_values["href"]);
+ }
+ };
+ /**
+ * Build Editable Mask Layer (EML) in order to show edit/delete actions
+ * on top of profile picture.
+ * @param {boolean} _noDelete disable delete button in initialization
+ */
+ et2_avatar.prototype._buildEditableLayer = function (_noDelete) {
+ var self = this;
+ // editable mask layer (eml)
+ var eml = jQuery(document.createElement('div'))
+ .addClass('eml')
+ .insertAfter(this.image);
+ // edit button
+ jQuery(document.createElement('div'))
+ .addClass('emlEdit')
+ .click(function () {
+ var buttons = [
+ { "button_id": 1, "text": self.egw().lang('save'), id: 'save', image: 'check', "default": true },
+ { "button_id": 0, "text": self.egw().lang('cancel'), id: 'cancel', image: 'cancelled' }
+ ];
+ var dialog = function (_title, _value, _buttons, _egw_or_appname) {
+ return et2_createWidget("dialog", {
+ callback: function (_buttons, _value) {
+ if (_buttons == 'save') {
+ var canvas = jQuery('#_cropper_image').cropper('getCroppedCanvas');
+ self.image.attr('src', canvas.toDataURL("image/jpeg", 1.0));
+ self.egw().json('addressbook.addressbook_ui.ajax_update_photo', [self.getInstanceManager().etemplate_exec_id, canvas.toDataURL('image/jpeg', 1.0)], function (res) {
+ if (res) {
+ del.show();
+ }
+ }).sendRequest();
+ }
+ },
+ title: _title || egw.lang('Input required'),
+ buttons: _buttons || et2_dialog.BUTTONS_OK_CANCEL,
+ value: {
+ content: _value
+ },
+ width: "90%",
+ height: "450",
+ resizable: false,
+ position: "top+10",
+ template: egw.webserverUrl + '/api/templates/default/avatar_edit.xet?2'
+ }, et2_dialog._create_parent(_egw_or_appname));
+ };
+ dialog(egw.lang('Edit avatar'), { photo: self.options.contact_id }, buttons, null);
+ })
+ .appendTo(eml);
+ // delete button
+ var del = jQuery(document.createElement('div'))
+ .addClass('emlDelete')
+ .click(function () {
+ et2_dialog.show_dialog(function (_btn) {
+ if (_btn == et2_dialog.YES_BUTTON) {
+ self.egw().json('addressbook.addressbook_ui.ajax_update_photo', [self.getInstanceManager().etemplate_exec_id, null], function (res) {
+ if (res) {
+ self.image.attr('src', '');
+ del.hide();
+ egw.refresh('Avatar Deleted.', egw.app_name());
+ }
+ }).sendRequest();
+ }
+ }, egw.lang('Delete this photo?'), egw.lang('Delete'), null, et2_dialog.BUTTONS_YES_NO);
+ })
+ .appendTo(eml);
+ if (_noDelete)
+ del.hide();
+ // invisible the mask
+ eml.css('opacity', '0');
+ eml.parent().css('position', "relative");
+ // bind handler for activating actions on editable mask
+ eml.on({
+ mouseover: function () { eml.css('opacity', '0.9'); },
+ mouseout: function () { eml.css('opacity', '0'); }
+ });
+ };
+ /**
+ * We need to build the Editable Mask Layer after widget gets loaded
+ */
+ et2_avatar.prototype.doLoadingFinished = function () {
+ _super.prototype.doLoadingFinished.call(this);
+ var self = this;
+ if (this.options.contact_id && this.options.editable) {
+ egw(window).json('addressbook.addressbook_ui.ajax_noPhotoExists', [this.options.contact_id], function (noPhotoExists) {
+ if (noPhotoExists)
+ self.image.attr('src', '');
+ self._buildEditableLayer(noPhotoExists);
+ }).sendRequest(true);
+ }
+ if (this.options.crop) {
+ jQuery(this.image).cropper({
+ aspectRatio: 1 / 1,
+ crop: function (e) {
+ console.log(e);
+ }
+ });
+ }
+ return true;
+ };
+ et2_avatar._attributes = {
+ "contact_id": {
+ name: "Contact id",
+ type: "string",
+ default: "",
+ description: "Contact id should be either user account_id {account:number} or contact_id {contact:number or number}"
+ },
+ "default_src": {
+ "ignore": true
+ },
+ "frame": {
+ name: "Avatar frame",
+ type: "string",
+ default: "circle",
+ description: "Define the shape of frame that avatar will be shown inside it. it can get {circle,rectangle} values which default value is cicle."
+ },
+ editable: {
+ name: "Edit avatar",
+ type: "boolean",
+ default: false,
+ description: "Make avatar widget editable to be able to crop profile picture or upload a new photo"
+ },
+ crop: {
+ name: "Crop avatar",
+ type: "boolean",
+ default: false,
+ description: "Create crop container and cropping feature"
+ }
+ };
+ /**
+ * background oolor codes
+ */
+ et2_avatar.LAVATAR_BG_COLORS = [
+ '#5a8770', '#b2b7bb', '#6fa9ab', '#f5af29',
+ '#0088b9', '#f18636', '#d93a37', '#a6b12e',
+ '#0088b9', '#f18636', '#d93a37', '#a6b12e',
+ '#5c9bbc', '#f5888d', '#9a89b5', '#407887',
+ '#9a89b5', '#5a8770', '#d33f33', '#a2b01f',
+ '#f0b126', '#0087bf', '#f18636', '#0087bf',
+ '#b2b7bb', '#72acae', '#9c8ab4', '#5a8770',
+ '#eeb424', '#407887'
+ ];
+ return et2_avatar;
+}(et2_image));
+et2_core_widget_1.et2_register_widget(et2_avatar, ["avatar"]);
/**
- * Avatar readonly widget to only display user profile picture or
- * user letter avatar based on user's firstname lastname.
- *
- * @augments et2_baseWidget
- */
-var et2_avatar_ro = (function(){ "use strict"; return et2_avatar.extend(
-{
- init: function ()
- {
- this._super.apply(this,arguments);
- this.options.editable = false;
- }
-
-});}).call(this);
-et2_register_widget(et2_avatar_ro, ["avatar_ro"]);
-
+* Avatar readonly widget to only display user profile picture or
+* user letter avatar based on user's firstname lastname.
+*/
+var et2_avatar_ro = /** @class */ (function (_super) {
+ __extends(et2_avatar_ro, _super);
+ function et2_avatar_ro(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_avatar_ro._attributes, _child || {})) || this;
+ _this.options.editable = false;
+ return _this;
+ }
+ return et2_avatar_ro;
+}(et2_avatar));
+et2_core_widget_1.et2_register_widget(et2_avatar_ro, ["avatar_ro"]);
/**
- * Letter Avatar widget to display user profile picture (given url) or
- * user letter avatar based on user's firstname lastname.
- *
- * It will use client-side lavatar if all the following conditions are met:
- * - contact_id, lname and fname are all set.
- * - the given src url includes flag of lavatar=1 which means there's
- * no personal avatar set for the contact yet.
- *
- * @augments et2_baseWidget
- */
-var et2_lavatar = (function(){ "use strict"; return et2_image.extend(
-{
- attributes: {
- lname: {
- name: "last name",
- type: "string",
- default: "",
- description:""
- },
- fname: {
- name: "first name",
- type: "string",
- default: "",
- description: ""
- },
-
- contact_id: {
- name: "contact id",
- type: "string",
- default: "",
- description: ""
- }
- },
-
- set_src: function(_url){
- if (_url && decodeURIComponent(_url).match("lavatar=1") && (this.options.fname || this.options.lname) && this.options.contact_id)
- {
- this.set_src(et2_avatar.lavatar(this.options.fname, this.options.lname, this.options.contact_id));
- return false;
- }
- this._super.apply(this,arguments);
- }
-});}).call(this);
-et2_register_widget(et2_lavatar, ["lavatar"]);
-
-jQuery.extend(et2_avatar,
-{
- /**
- * Function runs after uplaod in avatar dialog is finished and it tries to
- * update image and cropper container.
- *
- * @param {type} e
- * @returns {undefined}
- */
- uploadAvatar_onFinish: function (e){
- var file = e.data.resumable.files[0].file;
- var reader = new FileReader();
- reader.onload = function (e)
- {
- jQuery('#_cropper_image').attr('src', e.target.result);
- jQuery('#_cropper_image').cropper('replace',e.target.result);
- };
- reader.readAsDataURL(file);
- },
-
- /**
- * background oolor codes
- */
- LAVATAR_BG_COLORS: [
- '#5a8770', '#b2b7bb', '#6fa9ab', '#f5af29',
- '#0088b9', '#f18636', '#d93a37', '#a6b12e',
- '#0088b9', '#f18636', '#d93a37', '#a6b12e',
- '#5c9bbc', '#f5888d', '#9a89b5', '#407887',
- '#9a89b5', '#5a8770', '#d33f33', '#a2b01f',
- '#f0b126', '#0087bf', '#f18636', '#0087bf',
- '#b2b7bb', '#72acae', '#9c8ab4', '#5a8770',
- '#eeb424', '#407887'
- ],
-
- /**
- * Text color
- */
- LAVATAR_TEXT_COLOR: '#ffffff',
-
- LAVATAR_SIZE: 128,
- /**
- * Generate letter avatar with given data
- * @param {type} _fname
- * @param {type} _lname
- * @param {type} _id
- * @returns {string} return data url
- */
- lavatar: function(_fname, _lname, _id){
- var str = _fname + _lname + _id;
- var getBgColor = function(_str)
- {
- var hash = 0;
- for (var i=0; i< _str.length; i++)
- {
- hash = _str[i].charCodeAt() + hash;
- }
- return et2_avatar.LAVATAR_BG_COLORS[hash % et2_avatar.LAVATAR_BG_COLORS.length];
- };
- var bg = getBgColor(str);
- var size = et2_avatar.LAVATAR_SIZE * (window.devicePixelRatio ? window.devicePixelRatio : 1);
- var text = (_fname ? _fname[0].toUpperCase() : "")+(_lname ? _lname[0].toUpperCase() : "");
- var canvas = document.createElement('canvas');
- canvas.width = size;
- canvas.height = size;
- var context = canvas.getContext("2d");
- context.fillStyle = bg;
- context.fillRect (0, 0, canvas.width, canvas.height);
- context.font = Math.round(canvas.width/2)+"px Arial";
- context.textAlign = "center";
- context.fillStyle = et2_avatar.LAVATAR_TEXT_COLOR;
- context.fillText(text, size / 2, size / 1.5);
- var dataURL = canvas.toDataURL();
- canvas.remove();
- return dataURL;
- }
-});
\ No newline at end of file
+* Letter Avatar widget to display user profile picture (given url) or
+* user letter avatar based on user's firstname lastname.
+*
+* It will use client-side lavatar if all the following conditions are met:
+* - contact_id, lname and fname are all set.
+* - the given src url includes flag of lavatar=1 which means there's
+* no personal avatar set for the contact yet.
+*
+* @augments et2_baseWidget
+*/
+var et2_lavatar = /** @class */ (function (_super) {
+ __extends(et2_lavatar, _super);
+ function et2_lavatar(_parent, _attrs, _child) {
+ // Call the inherited constructor
+ return _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_lavatar._attributes, _child || {})) || this;
+ }
+ et2_lavatar.prototype.set_src = function (_url) {
+ if (_url && decodeURIComponent(_url).match("lavatar=1") && (this.options.fname || this.options.lname) && this.options.contact_id) {
+ this.set_src(et2_avatar.lavatar(this.options.fname, this.options.lname, this.options.contact_id));
+ return false;
+ }
+ _super.prototype.set_src.call(this, _url);
+ };
+ et2_lavatar._attributes = {
+ lname: {
+ name: "last name",
+ type: "string",
+ default: "",
+ description: ""
+ },
+ fname: {
+ name: "first name",
+ type: "string",
+ default: "",
+ description: ""
+ },
+ contact_id: {
+ name: "contact id",
+ type: "string",
+ default: "",
+ description: ""
+ }
+ };
+ return et2_lavatar;
+}(et2_image));
+et2_core_widget_1.et2_register_widget(et2_lavatar, ["lavatar"]);
+//# sourceMappingURL=et2_widget_image.js.map
\ No newline at end of file
diff --git a/api/js/etemplate/et2_widget_image.ts b/api/js/etemplate/et2_widget_image.ts
new file mode 100644
index 0000000000..7576538aae
--- /dev/null
+++ b/api/js/etemplate/et2_widget_image.ts
@@ -0,0 +1,717 @@
+/**
+ * EGroupware eTemplate2 - JS Description object
+ *
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage api
+ * @link http://www.egroupware.org
+ * @author Nathan Gray
+ * @copyright Nathan Gray 2011
+ * @version $Id$
+ */
+
+/*egw:uses
+ /vendor/bower-asset/jquery/dist/jquery.js;
+ et2_core_interfaces;
+ et2_core_baseWidget;
+ expose;
+ /vendor/bower-asset/cropper/dist/cropper.min.js;
+*/
+
+import {et2_baseWidget} from './et2_core_baseWidget';
+import {WidgetConfig, et2_register_widget} from "./et2_core_widget";
+import {ClassWithAttributes} from "./et2_core_inheritance";
+
+/**
+ * Class which implements the "image" XET-Tag
+ *
+ * @augments et2_baseWidget
+ */
+class et2_image extends et2_baseWidget implements et2_IDetachedDOM
+{
+ static readonly _attributes : any = {
+ "src": {
+ "name": "Image",
+ "type": "string",
+ "description": "Displayed image"
+ },
+ default_src: {
+ name: "Default image",
+ type: "string",
+ description: "Image to use if src is not found"
+ },
+ "href": {
+ "name": "Link Target",
+ "type": "string",
+ "description": "Link URL, empty if you don't wan't to display a link.",
+ "default": et2_no_init
+ },
+ "extra_link_target": {
+ "name": "Link target",
+ "type": "string",
+ "default": "_self",
+ "description": "Link target descriptor"
+ },
+ "extra_link_popup": {
+ "name": "Popup",
+ "type": "string",
+ "description": "widthxheight, if popup should be used, eg. 640x480"
+ },
+ "imagemap":{
+ // TODO: Do something with this
+ "name": "Image map",
+ "description": "Currently not implemented"
+ },
+ "label": {
+ "name": "Label",
+ "type": "string",
+ "description": "Label for image"
+ },
+ "expose_view":{
+ name: "Expose view",
+ type: "boolean",
+ default: false,
+ description: "Clicking on an image with href value would popup an expose view, and will show image referenced by href."
+ }
+ };
+
+ legacyOptions : string[] = ["href", "extra_link_target", "imagemap", "extra_link_popup", "id"];
+
+ image : JQuery = null;
+
+ /**
+ * Constructor
+ */
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_image._attributes, _child || {}));
+
+ // Create the image or a/image tag
+ this.image = jQuery(document.createElement("img"));
+ if (this.options.label)
+ {
+ this.image.attr("alt", this.options.label).attr("title", this.options.label);
+ }
+ if (this.options.href)
+ {
+ this.image.addClass('et2_clickable');
+ }
+ if(this.options["class"])
+ {
+ this.image.addClass(this.options["class"]);
+ }
+ this.setDOMNode(this.image[0]);
+ }
+
+ click(_ev : any)
+ {
+ if(this.options.href)
+ {
+ this.egw().open_link(this.options.href, this.options.extra_link_target, this.options.extra_link_popup);
+ }
+ else
+ {
+ super.click(_ev);
+ }
+ }
+
+ transformAttributes(_attrs : any)
+ {
+ super.transformAttributes(_attrs);
+
+ // Check to expand name
+ if (typeof _attrs["src"] != "undefined")
+ {
+ let manager = this.getArrayMgr("content");
+ if(manager) {
+ let src = manager.getEntry(_attrs["src"]);
+ if (typeof src != "undefined" && src !== null)
+ {
+ if(typeof src == "object")
+ {
+ src = egw().link('/index.php', src);
+ }
+ _attrs["src"] = src;
+ }
+ }
+ }
+ }
+
+ set_label(_value : string)
+ {
+ this.options.label = _value;
+ _value = this.egw().lang(_value);
+ // label is NOT the alt attribute in eTemplate, but the title/tooltip
+ this.image.attr("alt", _value).attr("title", _value);
+ }
+
+ setValue(_value)
+ {
+ // Value is src, images don't get IDs
+ this.set_src(_value);
+ }
+
+ set_href(_value) : boolean
+ {
+ if (!this.isInTree())
+ {
+ return false;
+ }
+
+ this.options.href = _value;
+ this.image.wrapAll('"');
+
+ let href = this.options.href;
+ let popup = this.options.extra_link_popup;
+ let target = this.options.extra_link_target;
+ let self = this;
+ this.image.click(function(e)
+ {
+ if (self.options.expose_view)
+ {
+ /*
+ TODO: Fix after implementing EXPOSE mixin class
+ */
+ //self._init_blueimp_gallery(e,_value);
+ e.stopImmediatePropagation();
+ }
+ else
+ {
+ egw.open_link(href,target,popup);
+ }
+
+ e.preventDefault();
+ return false;
+ });
+
+ return true;
+ }
+
+ /**
+ * Set image src
+ *
+ * @param {string} _value image, app/image or url
+ * @return {boolean} true if image was found, false if not (image is either not displayed or default_src is used)
+ */
+ set_src(_value : string) : boolean
+ {
+ if(!this.isInTree())
+ {
+ return false;
+ }
+
+ this.options.src = _value;
+
+ // allow url's too
+ if (_value[0] == '/' || _value.substr(0,4) == 'http' || _value.substr(0,5) == 'data:')
+ {
+ this.image.attr('src', _value).show();
+ return true;
+ }
+ let src = this.egw().image(_value);
+ if (src)
+ {
+ this.image.attr("src", src).show();
+ return true;
+ }
+ src = null;
+ if (this.options.default_src)
+ {
+ src = this.egw().image(this.options.default_src);
+ }
+ if (src)
+ {
+ this.image.attr("src", src).show();
+ }
+ else
+ {
+ this.image.css("display","none");
+ }
+ return false;
+ }
+
+ /**
+ * Function to get media content to feed the expose
+ * @param {type} _value
+ */
+ getMedia(_value) : object[]
+ {
+ let base_url = egw.webserverUrl.match(/^\/ig/)?egw(window).window.location.origin + egw.webserverUrl + '/':egw.webserverUrl + '/';
+ let mediaContent = [];
+ if (_value)
+ {
+ mediaContent = [{
+ title: this.options.label,
+ href: base_url + _value,
+ type: this.options.type + "/*",
+ thumbnail: base_url + _value
+ }];
+ }
+ return mediaContent;
+ }
+
+ /**
+ * Implementation of "et2_IDetachedDOM" for fast viewing in gridview
+ *
+ * @param {array} _attrs
+ */
+ getDetachedAttributes(_attrs)
+ {
+ _attrs.push("src", "label", "href");
+ }
+
+ getDetachedNodes()
+ {
+ return [this.image[0]];
+ }
+
+ setDetachedAttributes(_nodes, _values)
+ {
+ // Set the given DOM-Nodes
+ this.image = jQuery(_nodes[0]);
+
+ // Set the attributes
+ if (_values["src"])
+ {
+ this.set_src(_values["src"]);
+ }
+ // Not valid, but we'll deal
+ if (_values["value"])
+ {
+ this.setValue(_values["value"]);
+ }
+
+ if (_values["label"])
+ {
+ this.set_label(_values["label"]);
+ }
+ if(_values["href"])
+ {
+ this.image.addClass('et2_clickable');
+ this.set_href(_values["href"]);
+ }
+ }
+}
+et2_register_widget(et2_image, ["image"]);
+
+/**
+* Widget displaying an application icon
+*/
+class et2_appicon extends et2_image
+{
+ static readonly _attributes: any = {
+ default_src: {
+ name: "Default image",
+ type: "string",
+ default: "nonav",
+ description: "Image to use if there is no application icon"
+ }
+ };
+
+ set_src(_app) : boolean
+ {
+ if (!_app) _app = this.egw().app_name();
+ this.image.addClass('et2_appicon');
+ return super.set_src(_app == 'sitemgr-link' ? 'sitemgr/sitemgr-link' : // got removed from jdots
+ (this.egw().app(_app, 'icon_app') || _app)+'/'+(this.egw().app(_app, 'icon') || 'navbar'));
+ }
+}
+et2_register_widget(et2_appicon, ["appicon"]);
+
+/**
+* Avatar widget to display user profile picture or
+* user letter avatar based on user's firstname lastname.
+*
+* @augments et2_baseWidget
+*/
+
+class et2_avatar extends et2_image
+{
+ static readonly _attributes : any = {
+ "contact_id": {
+ name: "Contact id",
+ type: "string",
+ default: "",
+ description: "Contact id should be either user account_id {account:number} or contact_id {contact:number or number}"
+ },
+ "default_src": {
+ "ignore": true
+ },
+ "frame": {
+ name: "Avatar frame",
+ type: "string",
+ default: "circle",
+ description: "Define the shape of frame that avatar will be shown inside it. it can get {circle,rectangle} values which default value is cicle."
+ },
+ editable: {
+ name: "Edit avatar",
+ type: "boolean",
+ default: false,
+ description: "Make avatar widget editable to be able to crop profile picture or upload a new photo"
+ },
+ crop: {
+ name: "Crop avatar",
+ type: "boolean",
+ default: false,
+ description: "Create crop container and cropping feature"
+ }
+ };
+
+ /**
+ * background oolor codes
+ */
+ static LAVATAR_BG_COLORS : string[] = [
+ '#5a8770', '#b2b7bb', '#6fa9ab', '#f5af29',
+ '#0088b9', '#f18636', '#d93a37', '#a6b12e',
+ '#0088b9', '#f18636', '#d93a37', '#a6b12e',
+ '#5c9bbc', '#f5888d', '#9a89b5', '#407887',
+ '#9a89b5', '#5a8770', '#d33f33', '#a2b01f',
+ '#f0b126', '#0087bf', '#f18636', '#0087bf',
+ '#b2b7bb', '#72acae', '#9c8ab4', '#5a8770',
+ '#eeb424', '#407887'
+ ];
+
+ /**
+ * Text color
+ */
+ static LAVATAR_TEXT_COLOR: '#ffffff';
+
+ static LAVATAR_SIZE: 128;
+
+ /**
+ * Generate letter avatar with given data
+ * @param {type} _fname
+ * @param {type} _lname
+ * @param {type} _id
+ * @returns {string} return data url
+ */
+ static lavatar(_fname, _lname, _id)
+ {
+ let str = _fname + _lname + _id;
+ let getBgColor = function(_str)
+ {
+ let hash = 0;
+ for (let i=0; i< _str.length; i++)
+ {
+ hash = _str[i].charCodeAt(0) + hash;
+ }
+ return et2_avatar.LAVATAR_BG_COLORS[hash % et2_avatar.LAVATAR_BG_COLORS.length];
+ };
+ let bg = getBgColor(str);
+ let size = et2_avatar.LAVATAR_SIZE * (window.devicePixelRatio ? window.devicePixelRatio : 1);
+ let text = (_fname ? _fname[0].toUpperCase() : "")+(_lname ? _lname[0].toUpperCase() : "");
+ let canvas = document.createElement('canvas');
+ canvas.width = size;
+ canvas.height = size;
+ let context = canvas.getContext("2d");
+ context.fillStyle = bg;
+ context.fillRect (0, 0, canvas.width, canvas.height);
+ context.font = Math.round(canvas.width/2)+"px Arial";
+ context.textAlign = "center";
+ context.fillStyle = et2_avatar.LAVATAR_TEXT_COLOR;
+ context.fillText(text, size / 2, size / 1.5);
+ let dataURL = canvas.toDataURL();
+ canvas.remove();
+ return dataURL;
+ }
+
+ /**
+ * Function runs after uplaod in avatar dialog is finished and it tries to
+ * update image and cropper container.
+ * @param {type} e
+ */
+ static uploadAvatar_onFinish(e)
+ {
+ let file = e.data.resumable.files[0].file;
+ let reader = new FileReader();
+ reader.onload = function (e)
+ {
+ jQuery('#_cropper_image').attr('src', e.target.result);
+ jQuery('#_cropper_image').cropper('replace',e.target.result);
+ };
+ reader.readAsDataURL(file);
+ }
+
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_avatar._attributes, _child || {}));
+ if (this.options.frame == 'circle')
+ {
+ this.image.attr('style', 'border-radius:50%');
+ }
+ if (this.options.contact_id) this.setValue(this.options.contact_id);
+ }
+
+ /**
+ * Function to set contact id
+ * contact id could be in one of these formats:
+ * 'number', will be consider as contact_id
+ * 'contact:number', similar to above
+ * 'account:number', will be consider as account id
+ * @example: contact_id = "account:4"
+ *
+ * @param {string} _contact_id contact id could be as above mentioned formats
+ */
+ set_contact_id(_contact_id : string) : void
+ {
+ let params = {};
+ let id = 'contact_id';
+
+ this.image.addClass('et2_avatar');
+
+ if (!_contact_id)
+ {
+ _contact_id = this.egw().user('account_id');
+ }
+ else if(_contact_id.match(/account:/))
+ {
+ id = 'account_id';
+ _contact_id = _contact_id.replace('account:','');
+ }
+ else
+ {
+ id = 'contact_id';
+ _contact_id = _contact_id.replace('contact:', '');
+ }
+
+ // if our src (incl. cache-buster) already includes the correct id, use that one
+ if (this.options.src && this.options.src.match("(&|\\?)contact_id="+_contact_id+"(&|\\$)"))
+ {
+ return;
+ }
+ params[id] = _contact_id;
+ this.set_src(egw.link('/api/avatar.php',params));
+ }
+
+ /**
+ * Function to set value
+ */
+ setValue(_value : string)
+ {
+ this.set_contact_id(_value);
+ }
+
+ /**
+ * Implementation of "et2_IDetachedDOM" for fast viewing in gridview
+ */
+ getDetachedAttributes(_attrs : string[])
+ {
+ _attrs.push("contact_id", "label", "href");
+ }
+
+ setDetachedAttributes(_nodes, _values)
+ {
+ // Set the given DOM-Nodes
+ this.image = jQuery(_nodes[0]);
+
+ if (_values["contact_id"])
+ {
+ this.set_contact_id(_values["contact_id"]);
+ }
+
+ if (_values["label"])
+ {
+ this.set_label(_values["label"]);
+ }
+ if(_values["href"])
+ {
+ this.image.addClass('et2_clickable');
+ this.set_href(_values["href"]);
+ }
+ }
+
+ /**
+ * Build Editable Mask Layer (EML) in order to show edit/delete actions
+ * on top of profile picture.
+ * @param {boolean} _noDelete disable delete button in initialization
+ */
+ private _buildEditableLayer(_noDelete : boolean)
+ {
+ let self = this;
+ // editable mask layer (eml)
+ let eml = jQuery(document.createElement('div'))
+ .addClass('eml')
+ .insertAfter(this.image);
+
+ // edit button
+ jQuery(document.createElement('div'))
+ .addClass('emlEdit')
+ .click(function(){
+ let buttons = [
+ {"button_id": 1,"text": self.egw().lang('save'), id: 'save', image: 'check', "default":true},
+ {"button_id": 0,"text": self.egw().lang('cancel'), id: 'cancel', image: 'cancelled'}
+ ];
+ let dialog = function(_title, _value, _buttons, _egw_or_appname)
+ {
+ return et2_createWidget("dialog",
+ {
+ callback: function(_buttons, _value)
+ {
+ if (_buttons == 'save')
+ {
+ let canvas = jQuery('#_cropper_image').cropper('getCroppedCanvas');
+ self.image.attr('src', canvas.toDataURL("image/jpeg", 1.0));
+ self.egw().json('addressbook.addressbook_ui.ajax_update_photo',
+ [self.getInstanceManager().etemplate_exec_id, canvas.toDataURL('image/jpeg',1.0)],
+ function(res)
+ {
+ if (res)
+ {
+ del.show();
+ }
+ }).sendRequest();
+ }
+ },
+ title: _title||egw.lang('Input required'),
+ buttons: _buttons||et2_dialog.BUTTONS_OK_CANCEL,
+ value: {
+ content: _value
+ },
+ width: "90%",
+ height:"450",
+ resizable: false,
+ position:"top+10",
+ template: egw.webserverUrl+'/api/templates/default/avatar_edit.xet?2'
+ }, et2_dialog._create_parent(_egw_or_appname));
+ };
+
+ dialog(egw.lang('Edit avatar'),{photo:self.options.contact_id}, buttons, null);
+ })
+ .appendTo(eml);
+
+ // delete button
+ var del = jQuery(document.createElement('div'))
+ .addClass('emlDelete')
+ .click(function(){
+ et2_dialog.show_dialog(function(_btn){
+ if (_btn == et2_dialog.YES_BUTTON)
+ {
+ self.egw().json('addressbook.addressbook_ui.ajax_update_photo',
+ [self.getInstanceManager().etemplate_exec_id, null],
+ function(res)
+ {
+ if (res)
+ {
+ self.image.attr('src','');
+ del.hide();
+ egw.refresh('Avatar Deleted.', egw.app_name());
+ }
+ }).sendRequest();
+ }
+ }, egw.lang('Delete this photo?'), egw.lang('Delete'), null, et2_dialog.BUTTONS_YES_NO);
+ })
+ .appendTo(eml);
+ if (_noDelete) del.hide();
+ // invisible the mask
+ eml.css('opacity','0');
+
+ eml.parent().css('position', "relative");
+
+ // bind handler for activating actions on editable mask
+ eml.on({
+ mouseover:function(){eml.css('opacity','0.9');},
+ mouseout: function (){eml.css('opacity','0');}
+ });
+ }
+
+ /**
+ * We need to build the Editable Mask Layer after widget gets loaded
+ */
+ doLoadingFinished() : boolean
+ {
+ super.doLoadingFinished();
+ let self = this;
+ if (this.options.contact_id && this.options.editable)
+ {
+ egw(window).json(
+ 'addressbook.addressbook_ui.ajax_noPhotoExists',
+ [this.options.contact_id],
+ function(noPhotoExists)
+ {
+ if (noPhotoExists) self.image.attr('src','');
+ self._buildEditableLayer(noPhotoExists);
+ }
+ ).sendRequest(true);
+ }
+ if (this.options.crop)
+ {
+ jQuery(this.image).cropper({
+ aspectRatio: 1/1,
+ crop: function (e){
+ console.log (e);
+ }
+ });
+ }
+ return true;
+ }
+}
+et2_register_widget(et2_avatar, ["avatar"]);
+
+/**
+* Avatar readonly widget to only display user profile picture or
+* user letter avatar based on user's firstname lastname.
+*/
+class et2_avatar_ro extends et2_avatar
+{
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_avatar_ro._attributes, _child || {}));
+ this.options.editable = false;
+ }
+}
+et2_register_widget(et2_avatar_ro, ["avatar_ro"]);
+
+/**
+* Letter Avatar widget to display user profile picture (given url) or
+* user letter avatar based on user's firstname lastname.
+*
+* It will use client-side lavatar if all the following conditions are met:
+* - contact_id, lname and fname are all set.
+* - the given src url includes flag of lavatar=1 which means there's
+* no personal avatar set for the contact yet.
+*
+* @augments et2_baseWidget
+*/
+class et2_lavatar extends et2_image
+{
+ static readonly _attributes : any = {
+ lname: {
+ name: "last name",
+ type: "string",
+ default: "",
+ description:""
+ },
+ fname: {
+ name: "first name",
+ type: "string",
+ default: "",
+ description: ""
+ },
+
+ contact_id: {
+ name: "contact id",
+ type: "string",
+ default: "",
+ description: ""
+ }
+ };
+
+ constructor(_parent, _attrs? : WidgetConfig, _child? : object)
+ {
+ // Call the inherited constructor
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_lavatar._attributes, _child || {}));
+ }
+
+ set_src(_url){
+ if (_url && decodeURIComponent(_url).match("lavatar=1") && (this.options.fname || this.options.lname) && this.options.contact_id)
+ {
+ this.set_src(et2_avatar.lavatar(this.options.fname, this.options.lname, this.options.contact_id));
+ return false;
+ }
+ super.set_src(_url);
+ }
+}
+et2_register_widget(et2_lavatar, ["lavatar"]);
\ No newline at end of file