diff --git a/api/js/etemplate/Et2Description/Et2Description.ts b/api/js/etemplate/Et2Description/Et2Description.ts index 7183fb95f3..94bee0edc8 100644 --- a/api/js/etemplate/Et2Description/Et2Description.ts +++ b/api/js/etemplate/Et2Description/Et2Description.ts @@ -144,19 +144,19 @@ export class Et2Description extends Et2Widget(LitElement) implements et2_IDetach // If there's a link, wrap that - if(this.href && this._value) + if(this.href && this.value) { - render = this.wrapLink(this.href, this._value); + render = this.wrapLink(this.href, this.value); } // If we want to activate links inside, do that - else if(this.activate_links && this._value) + else if(this.activate_links && this.value) { - render = this.getActivatedValue(this._value, this.href ? this.extra_link_target : '_blank'); + render = this.getActivatedValue(this.value, this.href ? this.extra_link_target : '_blank'); } // Just do the value else { - render = html`${this._value}`; + render = html`${this.value}`; } return render; } @@ -185,11 +185,7 @@ export class Et2Description extends Et2Widget(LitElement) implements et2_IDetach // call super to get the onclick handling running super._handleClick(_ev); - if(this.expose_view && typeof this.mime != 'undefined' && this.mime_regexp && this.mime.match(this.mime_regexp, 'ig')) - { - this._init_blueimp_gallery(_ev, this.href); - } - else if(this.mime_data || this.href) + if(this.mime_data || this.href) { egw(window).open_link(this.mime_data || this.href, this.extra_link_target, this.extra_link_popup, null, null, this.mime); } diff --git a/api/js/etemplate/Expose/Et2DescriptionExpose.ts b/api/js/etemplate/Expose/Et2DescriptionExpose.ts new file mode 100644 index 0000000000..115d8f5c14 --- /dev/null +++ b/api/js/etemplate/Expose/Et2DescriptionExpose.ts @@ -0,0 +1,129 @@ +/** + * EGroupware eTemplate2 - Description that can expose + * + * @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 + * @copyright 2022 Nathan Gray + */ + +import {ExposeMixin, ExposeValue, MediaValue} from "./ExposeMixin"; +import {Et2Description} from "../Et2Description/Et2Description"; +import {et2_IDetachedDOM} from "../et2_core_interfaces"; +import {html} from "@lion/core"; + +/** + * Shows a description and if you click on it, it shows the file specified by href in gallery. + * + * If the gallery cannot handle the file type (specified by mime) then href is handled as + * a normal description, and clicking follows the link. + */ +//@ts-ignore Something not right with types & inheritance according to TypeScript +export class Et2DescriptionExpose extends ExposeMixin(Et2Description) implements et2_IDetachedDOM +{ + static get properties() + { + return { + ...super.properties, + + /** + * Mime type + * Used to determine this widget can be exposed. If not one of the OK mime types, will be treated + * as a normal description + */ + mime: { + type: String, + reflect: true + }, + + /** + * hash for data stored on service-side with egw_link::(get|set)_data() + */ + mime_data: {type: String}, + } + } + + constructor() {super();} + + connectedCallback() + { + super.connectedCallback(); + } + + disconnectedCallback() + { + super.disconnectedCallback(); + } + + /** These guys needed to get value where it needs to be */ + set value(new_value) + { + return super.value = new_value; + } + + get value() + { + return super.value; + } + + /** + * Needed to for transformAttributes() to set the value. + * Not sure why Et2Description.set_value() isn't enough. + */ + set_value(value) + { + super.set_value(value); + } + + /** + * Override the wrap link, since clicking on a link would work and do both + * @param href + * @param value + * @returns {TemplateResult<1>} + * @protected + */ + protected wrapLink(href, value) + { + if(this.isExposable()) + { + return html`${value}`; + } + else + { + // Expose cannot handle this particular file / link, wrap it as normal + return super.wrapLink(href, value); + } + } + + /** + * Used to determine if this widget is exposable. Images always are, even if we don't actually + * know the mime type. + * + * @returns {ExposeValue} + */ + get exposeValue() : ExposeValue + { + return { + mime: this.mime, + path: this.href, + download_url: this.href, + }; + } + + /** + * Get the info needed to show this image as slide(s) + */ + getMedia(_value) : MediaValue[] + { + let media = super.getMedia(_value); + if(media) + { + media[0].title = this.value; + } + return media; + } +} + +customElements.define("et2-description-expose", Et2DescriptionExpose as any); \ No newline at end of file diff --git a/api/js/etemplate/Expose/ExposeMixin.ts b/api/js/etemplate/Expose/ExposeMixin.ts index 45bf83f493..326b1036d7 100644 --- a/api/js/etemplate/Expose/ExposeMixin.ts +++ b/api/js/etemplate/Expose/ExposeMixin.ts @@ -72,9 +72,26 @@ export function ExposeMixin>(superclass : B) { return class extends superclass { + static get properties() + { + return { + ...super.properties, + + /** + * Function to extract an image list + * + * "Normally" we'll try to pull a list of images from the nextmatch or show just the current widget, + * but if you know better you can provide a method to get the list. + */ + mediaContentFunction: {type: Function}, + } + } + // @ts-ignore private _gallery : blueimp.Gallery; + private __mediaContentFunction : Function | null; + constructor(...args : any[]) { super(...args); @@ -111,7 +128,6 @@ export function ExposeMixin>(superclass : B) disconnectedCallback() { super.disconnectedCallback(); - this.removeEventListener("click", this.expose_onclick); } /** @@ -137,20 +153,40 @@ export function ExposeMixin>(superclass : B) */ getMedia(_value) : MediaValue[] { - let base_url = egw.webserverUrl.match(/^\/ig/) ? egw(window).window.location.origin + egw.webserverUrl + '/' : egw.webserverUrl + '/'; let mediaContent = []; if(_value) { mediaContent = [{ title: _value.label, - href: _value.download_url ? base_url + _value.download_url : base_url + _value.path, + href: _value.download_url ? this._processUrl(_value.download_url) : this._processUrl(_value.path), type: _value.mime || (_value.type ? _value.type + "/*" : "") }]; - mediaContent[0].thumbnail = _value.thumbnail ? (base_url + _value.thumbnail) : mediaContent[0].href; + if(this.isExposable()) + { + mediaContent[0].thumbnail = _value.thumbnail ? this._processUrl(_value.thumbnail) : mediaContent[0].href; + } + else + { + let fe = egw_get_file_editor_prefered_mimes(_value.mime); + if(fe && fe.mime[_value.mime] && fe.mime[_value.mime].favIconUrl) + { + mediaContent[0].thumbnail = fe.mime[_value.mime].favIconUrl; + } + } } return mediaContent; } + protected _processUrl(url) + { + let base_url = egw.webserverUrl.match(/^\/ig/) ? egw(window).window.location.origin + egw.webserverUrl + '/' : egw.webserverUrl + '/'; + if(base_url && base_url != '/' && url.indexOf(base_url) != 0) + { + url = base_url + url; + } + return url; + } + /** * Handle changes that have to happen based on changes to properties * @@ -173,12 +209,8 @@ export function ExposeMixin>(superclass : B) */ protected _bindGallery() { - //@ts-ignore Expects a parameter, but not actually required - let fe = egw_get_file_editor_prefered_mimes(); - // If the media type is not supported do not bind the click handler - if(!this.exposeValue || typeof this.exposeValue.mime != 'string' || (!this.exposeValue.mime.match(MIME_REGEX) - && (!fe || fe.mime && !fe.mime[this.exposeValue.mime])) || typeof this.exposeValue.download_url == 'undefined') + if(!this.isExposable()) { this.classList.remove("et2_clickable"); if(this._gallery) @@ -196,6 +228,19 @@ export function ExposeMixin>(superclass : B) } } + public isExposable() : boolean + { + if(!this.exposeValue || typeof this.exposeValue.mime !== "string") + { + return false + } + if(this.exposeValue.mime.match(MIME_REGEX) || this.exposeValue.mime.match(MIME_AUDIO_REGEX)) + { + return true; + } + return false; + } + /** * Just override the normal click handler * @@ -204,8 +249,11 @@ export function ExposeMixin>(superclass : B) */ _handleClick(_ev : MouseEvent) : boolean { - this.expose_onclick(_ev) - return true; + if((!this.isExposable() || this.expose_onclick(_ev)) && typeof super._handleClick === "function") + { + return super._handleClick(_ev); + } + return false; } get expose_options() @@ -410,7 +458,11 @@ export function ExposeMixin>(superclass : B) let options = this.expose_options; let nm = this.find_nextmatch(this); - if(nm && !this._is_target_indepth(nm, event.target)) + if(typeof this.__mediaContentFunction == "function") + { + this.__mediaContentFunction(this); + } + else if(nm && !this._is_target_indepth(nm, event.target)) { // Get the row that was clicked, find its index in the list let current_entry = nm.controller.getRowByNode(event.target); @@ -437,8 +489,24 @@ export function ExposeMixin>(superclass : B) } else { - // @ts-ignore - mediaContent = this.getMedia(_value); + // Try for all exposable of the same type in the parent widget + try + { + this.getParent().getDOMNode().querySelectorAll(this.localName).forEach((exposable, index) => + { + if(exposable === this) + { + options.index = index; + } + mediaContent.push(...exposable.getMedia(Object.assign({}, IMAGE_DEFAULT, exposable.exposeValue))); + }); + } + catch(e) + { + // Well, that didn't work. Just the one then. + // @ts-ignore + mediaContent = this.getMedia(_value); + } // Do not show thumbnail indicator on single expose view options.thumbnailIndicators = (mediaContent.length > 1); if(!options.thumbnailIndicators) @@ -652,23 +720,25 @@ export function ExposeMixin>(superclass : B) protected expose_onclick(event : MouseEvent) { - event.stopImmediatePropagation(); // Do not trigger expose view if one of the operator keys are held if(event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) { return; } + event.stopImmediatePropagation(); // @ts-ignore Wants an argument, but does not require it let fe = egw_get_file_editor_prefered_mimes(); if(this.exposeValue.mime.match(MIME_REGEX) && !this.exposeValue.mime.match(MIME_AUDIO_REGEX)) { this._init_blueimp_gallery(event, this.exposeValue); + return false; } else if(this.exposeValue.mime.match(MIME_AUDIO_REGEX)) { this._audio_player(this.exposeValue); + return false; } else if(fe && fe.mime && fe.edit && fe.mime[this.exposeValue.mime]) { @@ -677,8 +747,9 @@ export function ExposeMixin>(superclass : B) path: this.exposeValue.path, cd: 'no' // needed to not reload framework in sharing }), '', fe.edit_popup); + return false; } - + return true; } protected expose_onopen() {} @@ -840,6 +911,5 @@ export function ExposeMixin>(superclass : B) } protected expose_onclosed() {} - } } \ No newline at end of file diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index c464517923..bf297e8a16 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -38,6 +38,7 @@ import './Et2Date/Et2DateTimeToday'; import './Et2Description/Et2Description'; import './Et2Dialog/Et2Dialog'; import './Expose/Et2ImageExpose'; +import './Expose/Et2DescriptionExpose'; import './Et2Image/Et2Image'; import './Et2Select/Et2Select'; import './Et2Select/Et2SelectAccount';