From 4271b5c72c5969c0740059b29754ef3ce33a3a61 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 14 May 2024 08:17:06 -0600 Subject: [PATCH] Framework WIP: - Some work on taking over framework duties --- kdots/inc/class.kdots_framework.inc.php | 5 + kdots/js/EgwFramework.ts | 149 +++++++++++++++++++++++- kdots/js/EgwFrameworkApp.ts | 140 ++++++++++++++++++++-- 3 files changed, 283 insertions(+), 11 deletions(-) diff --git a/kdots/inc/class.kdots_framework.inc.php b/kdots/inc/class.kdots_framework.inc.php index a74681c13f..0ca4c1b2b8 100644 --- a/kdots/inc/class.kdots_framework.inc.php +++ b/kdots/inc/class.kdots_framework.inc.php @@ -53,6 +53,11 @@ class kdots_framework extends Api\Framework\Ajax $data['open_app_name'] = $open_app['name']; $data['open_app_url'] = $open_app['url']; } + if($data['open_app_name'] && !$this->sidebox_done) + { + $this->do_sidebox(); + $data['setSidebox'] = htmlentities(json_encode(static::$extra['setSidebox'], JSON_HEX_QUOT | JSON_HEX_AMP), ENT_QUOTES, 'UTF-8'); + } return $data; } diff --git a/kdots/js/EgwFramework.ts b/kdots/js/EgwFramework.ts index 5833295572..7f4820c3b3 100644 --- a/kdots/js/EgwFramework.ts +++ b/kdots/js/EgwFramework.ts @@ -103,6 +103,21 @@ export class EgwFramework extends LitElement private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");} + connectedCallback() + { + super.connectedCallback(); + if(this.egw.window && this.egw.window.opener == null && !this.egw.window.framework) + { + // This works, but stops a lot else from working + //this.egw.window.framework = this; + } + if(this.egw.window?.framework && this.egw.window?.framework !== this) + { + // Override framework setSidebox, use arrow function to force context + this.egw.framework.setSidebox = (applicationName, sideboxData, hash?) => this.setSidebox(applicationName, sideboxData, hash); + } + } + get egw() : typeof egw { return window.egw ?? { @@ -138,20 +153,44 @@ export class EgwFramework extends LitElement (menuaction ? '.' + menuaction[1] : ''); }; - public loadApp(appname, active = false) + /** + * Load an application into the framework + * + * Loading is done by name, and we look up everything we need in the applicationList + * + * @param {string} appname + * @param {boolean} active + * @param {string} url + * @returns {EgwFrameworkApp} + */ + public loadApp(appname : string, active = false, url = null) : EgwFrameworkApp { + const existing : EgwFrameworkApp = this.querySelector(`egw-app[name="${appname}"]`); + if(existing) + { + if(active) + { + this.tabs.show(appname); + } + if(url) + { + existing.url = url; + } + return existing; + } + const app = this.applicationList.find(a => a.name == appname); let appComponent = document.createElement("egw-app"); appComponent.id = appname; appComponent.name = appname; - appComponent.url = app?.url; + appComponent.url = url ?? app?.url; this.append(appComponent); // App was not in the tab list if(typeof app.opened == "undefined") { app.opened = this.shadowRoot.querySelectorAll("sl-tab").length; - this.requestUpdate("applicationList"); + this.requestUpdate("applicationList"); } // Wait until new tab is there to activate it @@ -166,6 +205,110 @@ export class EgwFramework extends LitElement return appComponent; } + /** + * Load a link into the framework + * + * @param {string} _link + * @param {string} _app + * @returns {undefined} + */ + public linkHandler(_link : string, _app : string) + { + //Determine the app string from the application parameter + let app = null; + if(_app && typeof _app == 'string') + { + app = this.applicationList.find(a => a.name == _app); + } + + if(!app) + { + //The app parameter was false or not a string or the application specified did not exists. + //Determine the target application from the link that had been passed to this function + app = this.parseAppFromUrl(_link); + } + + if(app) + { + if(_app == '_tab') + { + // add target flag + _link += '&target=_tab'; + const appname = app.appName + ":" + btoa(_link); + this.applicationList[appname] = {...app}; + this.applicationList[appname]['name'] = appname; + this.applicationList[appname]['indexUrl'] = _link; + this.applicationList[appname]['tab'] = null; + this.applicationList[appname]['browser'] = null; + this.applicationList[appname]['title'] = 'view'; + app = this.applicationList[appname]; + } + this.loadApp(app.name, true, _link); + } + else + { + //Display some error messages to have visible feedback + if(typeof _app == 'string') + { + egw_alertHandler('Application "' + _app + '" not found.', + 'The application "' + _app + '" the link "' + _link + '" points to is not registered.'); + } + else + { + egw_alertHandler("No appropriate target application has been found.", + "Target link: " + _link); + } + } + } + + /** + * Tries to obtain the application from a menuaction + * @param {string} _url + */ + protected parseAppFromUrl(_url : string) + { + let _app = null; + + // Check the menuaction parts from the url + let matches = _url.match(/menuaction=([a-z0-9_-]+)\./i) || + // Check the url for a scheme of "/app/something.php" + _url.match(/\/([^\/]+)\/[^\/]+\.php/i); + if(matches) + { + // check if this is a regular app-name + _app = this.applicationList.find(a => a.name == matches[1]); + } + + return _app; + } + + /** + * Print + */ + public async print() + { + const appElement : EgwFrameworkApp = this.querySelector("egw-app[active]"); + try + { + if(appElement) + { + await appElement.print(); + } + const appWindow = this.egw.window; + appWindow.setTimeout(appWindow.print, 0); + } + catch + { + // Ignore rejected + } + } + + public async setSidebox(appname, sideboxData, hash) + { + const app = this.loadApp(appname); + app.setSidebox(sideboxData, hash); + } + protected getBaseUrl() {return "";} /** diff --git a/kdots/js/EgwFrameworkApp.ts b/kdots/js/EgwFrameworkApp.ts index da459c1226..321419905c 100644 --- a/kdots/js/EgwFrameworkApp.ts +++ b/kdots/js/EgwFrameworkApp.ts @@ -10,6 +10,7 @@ import {SlSplitPanel} from "@shoelace-style/shoelace"; import {HasSlotController} from "../../api/js/etemplate/Et2Widget/slot"; import type {EgwFramework} from "./EgwFramework"; import {etemplate2} from "../../api/js/etemplate/etemplate2"; +import {et2_IPrint} from "../../api/js/etemplate/et2_core_interfaces"; /** * @summary Application component inside EgwFramework @@ -156,6 +157,14 @@ export class EgwFrameworkApp extends LitElement this.load(this.url); } + protected async getUpdateComplete() : Promise + { + const result = await super.updateComplete; + await this.loadingPromise; + + return result + } + protected load(url) { if(!url) @@ -231,6 +240,11 @@ export class EgwFrameworkApp extends LitElement } } + public setSidebox(sideboxData, hash?) + { + + } + public showLeft() { this.showSide("left"); @@ -251,6 +265,85 @@ export class EgwFrameworkApp extends LitElement this.hideSide("right"); } + public async print() + { + + let template; + let deferred = []; + let et2_list = []; + const appWindow = this.framework.egw.window; + + if((template = appWindow.etemplate2.getById(this.id)) && this == template.DOMContainer) + { + deferred = deferred.concat(template.print()); + et2_list.push(template); + } + else + { + // et2 inside, let its widgets prepare + this.querySelectorAll(":scope > *").forEach((domNode : HTMLElement) => + { + let et2 = appWindow.etemplate2.getById(domNode.id); + if(et2 && (domNode.offsetWidth > 0 || domNode.offsetHeight > 0 || domNode.getClientRects().length > 0)) + { + deferred = deferred.concat(et2.print()); + et2_list.push(et2); + } + }); + } + + if(et2_list.length) + { + // Try to clean up after - not guaranteed + let afterPrint = () => + { + this.egw.loading_prompt(this.name, true, this.egw.lang('please wait...'), this, egwIsMobile() ? 'horizental' : 'spinner'); + + // Give framework a chance to deal, then reset the etemplates + appWindow.setTimeout(function() + { + for(var i = 0; i < et2_list.length; i++) + { + et2_list[i].widgetContainer.iterateOver(function(_widget) + { + _widget.afterPrint(); + }, et2_list[i], et2_IPrint); + } + this.egw.loading_prompt(this.name, false); + }, 100); + appWindow.onafterprint = null; + }; + /* Not sure what this did, it triggers while preview is still up + if(appWindow.matchMedia) + { + var mediaQueryList = appWindow.matchMedia('print'); + var listener = function(mql) + { + if(!mql.matches) + { + mediaQueryList.removeListener(listener); + afterPrint(); + } + }; + mediaQueryList.addListener(listener); + } + + */ + + appWindow.addEventListener("afterprint", afterPrint, {once: true}); + + // Wait for everything to be ready + return Promise.all(deferred).catch((e) => + { + afterPrint(); + if(typeof e == "undefined") + { + throw "rejected"; + } + }); + } + } + protected showSide(side) { const attribute = `${side}Collapsed`; @@ -396,6 +489,44 @@ export class EgwFrameworkApp extends LitElement `; } + /** + * Top right header, contains application action buttons (reload, print, config) + * @returns {TemplateResult<1>} + * @protected + */ + protected _rightHeaderTemplate() + { + return html` + + + { + this.egw.refresh("", this.name); + /* Could also be this.load(false); this.load(this.url) */ + }} + > + this.framework.print()} + > + ${this.egw.user('apps')['waffles'] !== "undefined" ? html` + + { + // @ts-ignore + egw_link_handler(`/egroupware/index.php?menuaction=admin.admin_ui.index&load=admin.uiconfig.index&appname=${this.name}&ajax=true`, 'admin'); + }} + >` : nothing + } + + `; + } + render() { const hasLeftSlots = this.hasSideContent("left"); @@ -426,14 +557,7 @@ export class EgwFrameworkApp extends LitElement
${this.name} main-header
- - - - - + ${this._rightHeaderTemplate()}