2022-04-08 21:03:20 +02:00
|
|
|
/**
|
|
|
|
* EGroupware eTemplate2 - Image widget
|
|
|
|
*
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @package etemplate
|
|
|
|
* @subpackage api
|
|
|
|
* @link https://www.egroupware.org
|
|
|
|
* @author Nathan Gray
|
|
|
|
*/
|
|
|
|
|
2024-08-29 01:20:58 +02:00
|
|
|
import {html, LitElement} from "lit";
|
2022-04-08 21:03:20 +02:00
|
|
|
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
|
|
|
import {et2_IDetachedDOM} from "../et2_core_interfaces";
|
2024-08-14 18:44:05 +02:00
|
|
|
import {property} from "lit/decorators/property.js";
|
|
|
|
import {customElement} from "lit/decorators/custom-element.js";
|
2024-11-08 16:43:25 +01:00
|
|
|
import {until} from "lit/directives/until.js";
|
|
|
|
import {unsafeHTML} from "lit/directives/unsafe-html.js";
|
2022-04-08 21:03:20 +02:00
|
|
|
|
2024-08-14 18:44:05 +02:00
|
|
|
@customElement("et2-image")
|
2024-05-07 22:46:44 +02:00
|
|
|
export class Et2Image extends Et2Widget(LitElement) implements et2_IDetachedDOM
|
2022-04-08 21:03:20 +02:00
|
|
|
{
|
2024-08-29 01:20:58 +02:00
|
|
|
|
|
|
|
/** Et2Image has no shadow DOM, styles in etemplate2.css
|
2022-04-08 21:03:20 +02:00
|
|
|
static get styles()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
...super.styles,
|
|
|
|
css`
|
2024-05-07 22:46:44 +02:00
|
|
|
:host {
|
|
|
|
display: inline-block;
|
|
|
|
}
|
|
|
|
|
|
|
|
::slotted(img) {
|
|
|
|
max-height: 100%;
|
|
|
|
max-width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
:host([icon]) {
|
|
|
|
height: 1.3rem;
|
2024-08-15 09:25:18 +02:00
|
|
|
font-size: 1.3rem !important;
|
2024-05-07 22:46:44 +02:00
|
|
|
}
|
|
|
|
`];
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
2024-08-29 01:20:58 +02:00
|
|
|
*/
|
2022-04-08 21:03:20 +02:00
|
|
|
|
2024-08-14 18:44:05 +02:00
|
|
|
/**
|
|
|
|
* The label of the image
|
|
|
|
* Actually not used as label, but we put it as title
|
|
|
|
*/
|
|
|
|
@property({type: String})
|
|
|
|
label = "";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Image
|
|
|
|
* Displayed image
|
|
|
|
*/
|
|
|
|
@property({type: String})
|
2024-08-15 20:23:53 +02:00
|
|
|
set src(_src)
|
|
|
|
{
|
2024-09-26 19:35:41 +02:00
|
|
|
this.classList.forEach(_class =>
|
|
|
|
{
|
|
|
|
if(_class.startsWith('bi-'))
|
|
|
|
{
|
|
|
|
this.classList.remove(_class);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-08-15 20:23:53 +02:00
|
|
|
this.__src = _src;
|
|
|
|
let url = this.parse_href(_src) || this.parse_href(this.defaultSrc);
|
|
|
|
if(!url)
|
|
|
|
{
|
|
|
|
// Hide if no valid image
|
|
|
|
if (this._img) this._img.src = '';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const bootstrap = url.match(/\/node_modules\/bootstrap-icons\/icons\/([^.]+)\.svg/);
|
|
|
|
if (bootstrap && !this._img)
|
|
|
|
{
|
2024-09-26 19:35:41 +02:00
|
|
|
this.classList.add('bi-' + bootstrap[1]);
|
2024-08-15 20:23:53 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// change between bootstrap and regular img
|
|
|
|
this.requestUpdate();
|
|
|
|
}
|
|
|
|
get src()
|
|
|
|
{
|
|
|
|
return this.__src;
|
|
|
|
}
|
|
|
|
private __src: string;
|
2024-08-14 18:44:05 +02:00
|
|
|
/**
|
|
|
|
* Default image
|
|
|
|
* Image to use if src is not found
|
|
|
|
*/
|
|
|
|
@property({type: String})
|
|
|
|
defaultSrc = "";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Link Target
|
|
|
|
* Link URL, empty if you don't wan't to display a link.
|
|
|
|
*/
|
|
|
|
@property({type: String})
|
|
|
|
href = "";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Link target
|
|
|
|
* Link target descriptor
|
|
|
|
*/
|
|
|
|
@property({type: String})
|
|
|
|
extraLinkTarget = "_self";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Popup
|
|
|
|
* widthxheight, if popup should be used, eg. 640x480
|
|
|
|
*/
|
|
|
|
@property({type: String})
|
|
|
|
extraLinkPopup = "";
|
|
|
|
|
|
|
|
/**
|
2024-08-15 10:32:48 +02:00
|
|
|
* Width of image:
|
|
|
|
* - either number of px (e.g. 32) or
|
|
|
|
* - string incl. CSS unit (e.g. "32px") or
|
|
|
|
* - even CSS functions like e.g. "calc(1rem + 2px)"
|
2024-08-14 18:44:05 +02:00
|
|
|
*/
|
|
|
|
@property({type: String})
|
2024-08-15 20:23:53 +02:00
|
|
|
set width(_width : string)
|
|
|
|
{
|
|
|
|
if (this.style)
|
|
|
|
{
|
|
|
|
this.style.width = isNaN(_width) ? _width : _width+'px';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
get width()
|
|
|
|
{
|
|
|
|
return this.style?.width;
|
|
|
|
}
|
2024-08-14 18:44:05 +02:00
|
|
|
|
|
|
|
/**
|
2024-08-15 10:32:48 +02:00
|
|
|
* Height of image:
|
|
|
|
* - either number of px (e.g. 32) or
|
|
|
|
* - string incl. CSS unit (e.g. "32px") or
|
|
|
|
* - even CSS functions like e.g. "calc(1rem + 2px)"
|
2024-08-14 18:44:05 +02:00
|
|
|
*/
|
|
|
|
@property({type: String})
|
2024-08-15 20:23:53 +02:00
|
|
|
set height(_height)
|
|
|
|
{
|
|
|
|
if (this.style)
|
2024-08-16 10:19:13 +02:00
|
|
|
{
|
|
|
|
this.style.height = isNaN(_height) ? _height : _height+'px';
|
|
|
|
}
|
2024-08-15 20:23:53 +02:00
|
|
|
}
|
|
|
|
get height()
|
|
|
|
{
|
|
|
|
return this.style.height;
|
|
|
|
}
|
2024-08-14 18:44:05 +02:00
|
|
|
|
2022-04-08 21:03:20 +02:00
|
|
|
constructor()
|
|
|
|
{
|
|
|
|
super();
|
2022-05-05 01:31:42 +02:00
|
|
|
|
|
|
|
this._handleClick = this._handleClick.bind(this);
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback()
|
|
|
|
{
|
|
|
|
super.connectedCallback();
|
|
|
|
}
|
|
|
|
|
2024-08-14 18:44:05 +02:00
|
|
|
render()
|
2022-04-08 21:03:20 +02:00
|
|
|
{
|
2024-08-15 20:23:53 +02:00
|
|
|
let url = this.parse_href(this.src) || this.parse_href(this.defaultSrc);
|
|
|
|
if(!url)
|
2022-04-12 18:00:43 +02:00
|
|
|
{
|
|
|
|
// Hide if no valid image
|
2024-08-14 18:44:05 +02:00
|
|
|
return html``;
|
2022-04-12 18:00:43 +02:00
|
|
|
}
|
2024-09-19 10:47:42 +02:00
|
|
|
// set title on et2-image for both bootstrap-image via css-class and embedded img tag
|
|
|
|
this.title = this.statustext || this.label || "";
|
|
|
|
|
2024-08-15 20:23:53 +02:00
|
|
|
const bootstrap = url.match(/\/node_modules\/bootstrap-icons\/icons\/([^.]+)\.svg/);
|
2024-08-15 09:25:18 +02:00
|
|
|
if (bootstrap)
|
|
|
|
{
|
2024-09-26 15:21:31 +02:00
|
|
|
this.classList.add('bi-'+bootstrap[1]);
|
2024-08-15 09:25:18 +02:00
|
|
|
return html``;
|
|
|
|
}
|
2024-11-08 16:43:25 +01:00
|
|
|
|
|
|
|
// our own svg images
|
2024-11-12 10:50:51 +01:00
|
|
|
// We have svg images prefixed "bi-". These are used like bootstrap font icons.
|
|
|
|
// We inline them to be able to control there color etc. directly via css
|
|
|
|
|
2024-11-08 16:43:25 +01:00
|
|
|
//only call unsafeHtml when we are inside /egroupware/
|
2024-11-11 16:32:54 +01:00
|
|
|
const ourSvg = url.startsWith(this.egw().webserverUrl + '/') //checks if source is trusted
|
|
|
|
if (ourSvg && url.match(/\/bi-.*\.svg/))
|
2024-11-08 16:43:25 +01:00
|
|
|
{
|
|
|
|
const svg = fetch(url)
|
|
|
|
.then(res => res.text()
|
|
|
|
.then(text => unsafeHTML(text)));
|
|
|
|
return html`
|
|
|
|
${until(svg, html`<span>...</span>`)}
|
|
|
|
`
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback case (no svg, web source)
|
2022-04-08 21:03:20 +02:00
|
|
|
return html`
|
|
|
|
<img ${this.id ? html`id="${this.id}"` : ''}
|
2024-08-15 20:23:53 +02:00
|
|
|
src="${url}"
|
2024-09-06 13:05:37 +02:00
|
|
|
alt="${this.label || this.statustext}"
|
2024-09-19 14:57:08 +02:00
|
|
|
style="${this.height ? 'max-height: 100%; width: auto' : 'max-width: 100%; height: auto'}"
|
2022-05-27 22:10:57 +02:00
|
|
|
part="image"
|
2023-06-15 17:27:59 +02:00
|
|
|
loading="lazy"
|
2022-04-08 21:03:20 +02:00
|
|
|
>`;
|
|
|
|
}
|
|
|
|
|
2024-08-14 18:44:05 +02:00
|
|
|
/**
|
|
|
|
* Puts the rendered content / img-tag in light DOM
|
|
|
|
* @link https://lit.dev/docs/components/shadow-dom/#implementing-createrenderroot
|
|
|
|
*/
|
|
|
|
protected createRenderRoot()
|
2022-04-13 18:37:55 +02:00
|
|
|
{
|
2024-08-14 18:44:05 +02:00
|
|
|
return this;
|
2022-04-13 18:37:55 +02:00
|
|
|
}
|
|
|
|
|
2022-04-11 21:38:24 +02:00
|
|
|
protected parse_href(img_href : string) : string
|
|
|
|
{
|
2022-08-31 12:24:49 +02:00
|
|
|
img_href = img_href || '';
|
2022-04-08 21:03:20 +02:00
|
|
|
// allow url's too
|
2022-04-11 21:38:24 +02:00
|
|
|
if(img_href[0] == '/' || img_href.substr(0, 4) == 'http' || img_href.substr(0, 5) == 'data:')
|
2022-04-08 21:03:20 +02:00
|
|
|
{
|
2022-04-11 21:38:24 +02:00
|
|
|
return img_href;
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
2024-09-09 18:20:46 +02:00
|
|
|
let src = this.egw() && typeof this.egw().image == "function" ? this.egw()?.image(img_href) : "";
|
2022-04-08 21:03:20 +02:00
|
|
|
if(src)
|
|
|
|
{
|
2022-04-11 21:38:24 +02:00
|
|
|
return src;
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
2022-04-11 21:38:24 +02:00
|
|
|
return "";
|
2022-04-08 22:03:29 +02:00
|
|
|
}
|
|
|
|
|
2022-04-08 21:03:20 +02:00
|
|
|
_handleClick(_ev : MouseEvent) : boolean
|
|
|
|
{
|
|
|
|
if(this.href)
|
|
|
|
{
|
2022-07-21 17:57:50 +02:00
|
|
|
this.egw().open_link(this.href, this.extraLinkTarget, this.extraLinkPopup);
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return super._handleClick(_ev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 18:37:55 +02:00
|
|
|
get _img()
|
|
|
|
{
|
2022-05-27 22:10:57 +02:00
|
|
|
return this.querySelector('img');
|
2022-04-13 18:37:55 +02:00
|
|
|
}
|
|
|
|
|
2022-04-08 21:03:20 +02:00
|
|
|
/**
|
|
|
|
* Handle changes that have to happen based on changes to properties
|
|
|
|
*
|
|
|
|
*/
|
2022-04-13 16:34:24 +02:00
|
|
|
updated(changedProperties)
|
2022-04-08 21:03:20 +02:00
|
|
|
{
|
2022-04-13 16:34:24 +02:00
|
|
|
super.updated(changedProperties);
|
2022-04-08 21:03:20 +02:00
|
|
|
|
2022-04-13 16:34:24 +02:00
|
|
|
// if there's an href or onclick, make it look clickable
|
2022-06-10 21:25:54 +02:00
|
|
|
if(changedProperties.has("href") || typeof this.onclick !== "undefined")
|
2022-04-08 21:03:20 +02:00
|
|
|
{
|
2022-06-10 21:25:54 +02:00
|
|
|
this.classList.toggle("et2_clickable", this.href || typeof this.onclick !== "undefined")
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
2022-04-13 18:37:55 +02:00
|
|
|
for(const changedPropertiesKey in changedProperties)
|
|
|
|
{
|
2024-09-06 13:05:37 +02:00
|
|
|
if(Et2Image.getPropertyOptions()[changedPropertiesKey] &&
|
|
|
|
!(changedPropertiesKey === 'label' || changedPropertiesKey === 'statustext'))
|
2022-04-13 18:37:55 +02:00
|
|
|
{
|
|
|
|
this._img[changedPropertiesKey] = this[changedPropertiesKey];
|
|
|
|
}
|
|
|
|
}
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
|
|
|
|
2022-04-11 23:03:24 +02:00
|
|
|
transformAttributes(_attrs : any)
|
|
|
|
{
|
|
|
|
super.transformAttributes(_attrs);
|
|
|
|
|
2022-08-15 21:25:08 +02:00
|
|
|
// Expand src with additional stuff
|
|
|
|
// This should go away, since we're not checking for $ or @
|
2022-04-11 23:03:24 +02:00
|
|
|
if(typeof _attrs["src"] != "undefined")
|
|
|
|
{
|
|
|
|
let manager = this.getArrayMgr("content");
|
|
|
|
if(manager && _attrs["src"])
|
|
|
|
{
|
|
|
|
let src = manager.getEntry(_attrs["src"], false, true);
|
|
|
|
if(typeof src != "undefined" && src !== null)
|
|
|
|
{
|
|
|
|
if(typeof src == "object")
|
|
|
|
{
|
2022-08-15 21:25:08 +02:00
|
|
|
this.src = this.egw().link('/index.php', src);
|
|
|
|
}
|
2022-09-28 21:36:56 +02:00
|
|
|
else
|
2022-08-15 21:25:08 +02:00
|
|
|
{
|
|
|
|
this.src = src;
|
2022-04-11 23:03:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-08 21:03:20 +02:00
|
|
|
/**
|
|
|
|
* Code for implementing et2_IDetachedDOM
|
|
|
|
*
|
|
|
|
* Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
|
|
|
|
*
|
|
|
|
* @param {array} _attrs array to add further attributes to
|
|
|
|
*/
|
|
|
|
getDetachedAttributes(_attrs)
|
|
|
|
{
|
2023-04-25 21:53:16 +02:00
|
|
|
_attrs.push("src", "label", "href", "statustext");
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getDetachedNodes()
|
|
|
|
{
|
2022-04-11 21:38:24 +02:00
|
|
|
return [<HTMLElement><unknown>this];
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
setDetachedAttributes(_nodes, _values)
|
|
|
|
{
|
2022-05-03 19:01:42 +02:00
|
|
|
for(let attr in _values)
|
|
|
|
{
|
|
|
|
this[attr] = _values[attr];
|
|
|
|
}
|
2022-04-08 21:03:20 +02:00
|
|
|
}
|
2024-08-14 18:44:05 +02:00
|
|
|
}
|